线程(Thread)
多线程:
多线程使Java语言的重要特性,大量应用于网络编程、服务器端程序的开发,最常见的UI界面底层原理、操作系统底层原理都大量使用了多线程。
程序:
“程序(Program)”是一个静态的概念,一般对应与操作系统中的一个可执行文件,比如:我们要启动网易音乐,则对应酷狗的可执行程序。当我们双击网易,则加载到内存中,开始执行该程序,于时产生了“进程”。
进程:
- 执行中的程序叫做进程(Process),是一个动态的概念。现代的操作系统都可以同时启动多个进程。比如同时启动多个应用程序。
- 进程的特点:
- 进程是程序的一次动态执行过程,占用特定的地址空间。
- 每个进程由3部分组成:Cpu、data、code。每个进程都是独立的,保有自己的Cpu时间,代码和数据,即便用同一份程序产生好几个进程,他们之间还是拥有自己的这三样东西,这样的缺点是:浪费内存,CPU的负担较重。
- 多任务(Multitasking)操作系统将CPU时间动态规划地分给某个进程,操作系统同时执行多个进程,你每个进程独立的运行。以进程的观点来看,它会以自己独占CPU的使用权。
线程:
- 一个进程可以产生多个线程。同多个进程可以共享操作系统里的某些资源一样,同一进程的多个线程也可以共享此进程的某些资源(code,data),所以线程又被称为轻量级进程(lightweight process)。
- 一个进程内部的一个执行单元,他是程序中的一个单一的顺序控制流程。
- 一个进程可以拥有多个并行的(concurrent)线程。
- 一个进程中的多个线程共享相同的内存单元/内存地址空间,可以访问相同的变量和对象,而且他们从同一堆中分配对象那个并进行通信、数据交换和同步操作。
- 由于线程间的通信是在同一地址空间上进行的,多以不需要额外的通信机制,这就使得通信更简便而且信息传递的速度也更快。
- 线程的启动、中断、消亡,消耗的资源非常少。
线程和进程的区别:
- 每个进程都有独立的代码和数据空间,进程间的切换会有较大的开销。
- 线程可以看成是轻量级的进程,属于同一进程的线程共享代码和数据空间,每个线程又独立的运行栈和程序计数器(PC),线程切换到开销小。
- 线程和进程的最根本的区别在于:进程是资源分配的单位,线程是调度和执行的单位。
- 多进程:在操作系统中能同时运行多个任务(程序)。
- 多线程:在同一应用程序中有多个顺序流同时执行。
- 线程是进程的一部分,所以线程有的时候被称为轻量级进程。
- 一个没有线程的进程是可以被看作单线程的,如果一个进程内拥有多个线程,进程的执行过程不是一条线(线程)的,而是多条线(线程)共同完成的。
- 系统在运行的时候会为每个进程分配不同的内存区域,但是不会为了线程分配内存(线程所使用的资源是它所属的进程的资源),线程组只能共享资源。那就是说,除了CPU之外(线程在运行的时候要占用CPU资源),计算机内部的软硬件资源的配置与线程无关,线程只能共享它所属进程的资源。
进程与程序的区别:
程序是一组指令的集合,它是静态的实体,没有执行的含义。而进程是一个动态的实体,有自己的生命周期。以吧来说,一个进程肯定与一个程序相对应,并且只有一个,但是一个程序可以有多个进程,或者一个进程都没有。除此之外,进程还有并发性和交往性。简单来说:进程是程序的一部分,程序运行的时候会产生进程。
通过继承Thread类实现多线程
- 在Java中负责实现线程功能的类是java.lang.Thread类。
- 可以通过创建Thread的实例来创建新的线程。
- 每个吸纳成都是通过某个特定的Thread对象所对应的方法
run()
来完成其操作,方法run()称为线程体。 - 通过调用Thread类的==start()==方法来启动一个线程。
public class TestThread extends Thread { // 自定义类继承Thread类
// run()方法里是线程体
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(this.getName() + ":" + i); // getName()方法返回线程名称
}
}
public static void main(String[] args) {
TestThread thread1 = new TestThread(); // 创建线程对象
thread1.start(); // 启动线程
TestThread thread2 = new TestThread();
thread2.start();
}
}
通过Runnable接口实现多线程
- 在应用开发中,我们应用更多的是通过
Runnable
接口实现多线程。这种方式克服了 Thread 实现线程类的缺点,即实现Runnable
接口的同时还可以继承某个类。 - 所以实现
Runnable
接口的方式要通用一些。
public class TestThread2 implements Runnable { // 自定义类实现Runable接口
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
public static void main(String[] args) {
// 创建线程对象,把实现了Runnable接口的对象作为参数传入;
Thread thread1 = new Thread(new TestThread2());
thread1.start(); // 启动线程
Thread thread2 = new Thread(new TestThread2());
thread2.start(); // 启动线程
}
}
通过Callable实现多线程
public class CDownloader implements Callable<Boolean> {
private String url;
private String name;
public CDownloader(String url, String name) {
this.url = url;
this.name = name;
}
@Override
public Boolean call() throws Exception{
WebDownloader wd = new WebDownloader(); // WebDownloader为一个web下载的类
wd.download(url, name);
System.out.println(name);
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
CDownloader cd1 = new CDownloader("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1588907409428&di=b08ec32df49d24e0153d046b5e37e86e&imgtype=0&src=http%3A%2F%2Fqimg.hxnews.com%2F2018%2F0611%2F1528676232318.jpg","火箭熊.jpg");
CDownloader cd2 = new CDownloader("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1588907409428&di=315902600665b2854ac20e3a60a8ec2a&imgtype=0&src=http%3A%2F%2Fimg.zcool.cn%2Fcommunity%2F0105d15724c5886ac725381222c109.jpg%402o.jpg","蜘蛛侠.jpg");
CDownloader cd3 = new CDownloader("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1588907837238&di=5519b9b715f0276bb29fc1e07497db54&imgtype=0&src=http%3A%2F%2Fimg.zcool.cn%2Fcommunity%2F01850455450dc50000019ae92d459c.jpg%401280w_1l_2o_100sh.jpg","蜘蛛侠02.jpg");
// 创建执行服务
ExecutorService service = Executors.newFixedThreadPool(3);
// 提交执行
Future<Boolean> reslut1 = service.submit(cd1);
Future<Boolean> reslut2 = service.submit(cd2);
Future<Boolean> reslut3 = service.submit(cd3);
// 获取结果
boolean r1 = reslut1.get();
boolean r2 = reslut2.get();
boolean r3 = reslut3.get();
System.out.println(r3);
// 关闭服务
service.shutdownNow();
}
}
线程状态
新生状态(New)
- 用new关键字建立一个线程对象后,该线程对象就处于新生状态。
- 处于新生状态的线程有自己的内存空间,通过调用start方法进入就绪状态。
就绪状态(Runnable)
处于就绪状态的线程已经具备了运行条件,但是还没有被分配到CPU,处于“线程就绪队列”,等待系统为其分配CPU。就绪状态并不是执行状态,当系统选定一个等待执行的Thread对象后,她就会进入执行状态。一旦获得CPU,线程就进入运行状态并自动调用自己的run()方法。
线程进入就绪状态的4个原因:
- 新建线程: 调用start()方法,进入就绪状态。
- 阻塞线程: 阻塞解除,进入就绪状态。
- 运行进程: 调用yield(),直接进入就绪状态。
- 运行状态: JVM 将 CPU 资源从本线程切换到其他线程。
运行状态(Running)
在运行状态的线程执行自己run()方法中的代码,直到调用其他方法而终止或等待某资源而阻塞或完成任务而死亡。
如果再给定的时间片内没有执行结束,就会被系统给换下来回到就绪状态。也可能由于某些“导致阻塞的事件”而进入阻塞状态。
阻塞状态(Blocked)
阻塞指的是暂停一个线程的执行以等待某个条件发生(如某资源就绪)。有4中原因会导致阻塞。
- 执行sleep(int millsecond) 方法,使当前线程休眠,进入阻塞状态。当指定的时间到了后,线程进入就绪状态。
- 执行wait()方法,使当前线程进入阻塞状态。当使用nofity()方法唤醒这个线程后,他进入就绪状态。
- 线程运行时,某个操作进入阻塞状态,比如执行IO流操作(read()/write()方法本身就是阻塞方法的方法)。只有当引起该操作阻塞的原因消失后,线程进入就绪状态。
- join() 线程联合:当某个线程等待另一个线程执行结束后,才能继续执行,使用join()方法。
死亡状态(Terminated)
- 死亡状态是线程声明周期中最后一个阶段。线程死亡的原因有两个。一个是正常运行的线程完成了它run()方法内的全部工作;另一个是线程被强制终止,如通过执行stop()或destroy()方法来终止一个线程(注:stop()/destroy()方法已经被JDK废弃,不推荐使用)。
- 当一个线程进入死亡状态以后,就不能再回到其他状态了。
线程终止的典型方式
终止线程我们一般不使用JDK提供的stop()/destory()方法(它们本身也被JDK废弃了)。通常我们的做法是提供一个boolean型的终止变量,当这个变量置为false,则终止线程的运行。
public class TestThreadCiycle implements Runable {
String name;
boolean live = true; // 标记变量。表示线程是否可终止。
public TestThreadCiycle(String name) {
super();
this.name = name;
}
@Override
public void run() {
int i = 0;
// 当live的值为true时,继续线程体,false则结束循环,继而终止线程体。
while(live) {
System.out.println(name + (i++));
}
}
public void terminate() {
live = false;
}
public static void main(String[] args) {
TestThreadCiycle ttc = new TestThreadCiycle("线程A:");
Thread t1 = new Thread(ttc);// 新生状态
t1.start(); //就绪状态
for(int i = 0; i < 100) {
System.out.println("主线程:" + i);
}
ttc.terminate();
System.out.println("ttc stop!");
}
}
暂停线程执行
暂停线程常用的方法有sleep()和yield()方法,这俩方法区别如下:
sleep()
方法:可以让正在运行的线程进入阻塞状态,直到休眠时间满了,进入就绪状态。yield()
方法:可以让正在运行的线程直接进入就绪状态,让出CPU的使用权。- 使用
yield()
可以引起线程的切换,但运行时没有明显延迟。
// sleep()
public class TestThreadState {
public static void main(String[] args) {
StateThread thread1 = new StateThread();
thread1.start();
StateThread thread2 = new StateThread();
thraed2.start();
}
}
// 使用继承的方式实现多线程
class StateThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(this.getName + ":" + i);
try {
Thread.sleep(2000); // 调用线程的sleep()方法
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// yield()
public class TestThreadState {
public static void main(String[] args) {
Thread thread1 = new Thread(new StateThread);
Thread thread2 = new Thread(new StateThread);
thread1.start();
thread2.start();
}
}
// 通过接口Runnable实现多线程
class StateThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread.getName() + ":" + i);
Thread.yield(); // 调用线程的yield()方法。
}
}
}
守护线程(Daemon)
守护线程顾名思义,就是保佑其他线程正常运行,不必管自己本身。专业一点:守护线程:是为用户线程服务的;JVM停止不用等待守护线程执行完毕。(没有守护线程的话,用户线程JVM需要等待所有的线程执行完毕,才会停止)
public class DaemonTest {
public static void main(String[] args) {
God god = new God();
You you = new You();
// 代理
Thread t = new Thread(god);
t.setDaemon(true);
// 将用户线程调整为守护线程,守护线程就是通过这儿设置的。这儿也就是将god线程设置为了守护线程。
t.start();
new Thread(you).start();
}
}
class You implements Runnable{
@Override
public void run() {
for (int i = 0; i < 365*100; i++) {
System.out.println("happy life...");
}
System.out.println("oooooooooooo...");
}
}
class God implements Runnable{
@Override
public void run() {
for (int i = 0; i < 365*1000000; i++) {
System.out.println("bless you...");
}
System.out.println("oooooooooooo...");
}
}
线程的联合join()
线程A再运行期间,可以调用线程B的join()方法,让线程B和线程A联合。这样,线程A就必须等待线程B执行完毕后,才能继续执行。如下例:“爸爸线程”要抽烟,于是联合“儿子线程”去买烟,必须等待“儿子线程”买烟完毕,“爸爸线程”才能继续抽烟。
public class TestThreadState {
public static void mian(String[] args) {
System.out.println("爸爸和儿子买烟的故事!");
Thread father = new Thread(new FatherThread());
father.start();
}
}
class FatherThread implements Runable {
@Override
public void run() {
System.out.println("爸爸想抽烟,发现烟抽完了!?");
System.out.println("爸爸让儿子去买包烟");
Thread son = new Thread(new SonThread());
son.start();
System.out.println("爸爸等儿子买烟回来");
try {
son.join();
} catch (INterruptedException e) {
e.printStackTrace();
System.out.println("爸爸出门找儿子跑哪去了");
System.exit(1);
}
System.out.println("爸爸高兴的接过烟开始抽,并把零钱给了儿子");
}
}
class SonThread implements {
@Override
public void run() {
System.out.println("儿子出门买烟");
System.out.println("买烟需要10分钟");
try {
for (int i = 1; i <= 10; i++) {
System.out.println("第" + i + "分钟");
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("儿子买烟回来了");
}
}
运行结果为:
获取线程基本信息的方法
方法 | 功能 |
---|---|
isAlive() | 判断线程是否还活着,即线程是否还未终止 |
getPriority() | 获得线程的优先级数值 |
setPriority() | 设置线程的优先级数值 |
setName() | 给线程一个名字 |
getName() | 获得线程的名字 |
currentThread() | 获得房前正在运行的线程对象,也就是取得自己本身 |
public class TestThread {
public static void main(String[] args) throws Exception {
Runnable r = new MyThread();
Thread t = new Thread(r, "Name test"); // 定义线程对象,并传入参数;
t.start(); // 启动线程
System.out.println("Name is:" + t.getName()); // 输出线程名称
Thread.currentThread().sleep(5000); // 线程暂停5s。
System.out.println(t.isAlive()); // 判断线程还在运行吗?
System.out.println("over!");
}
}
class MyThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
}
线程的优先级
优先级低只是意味着获得调度的概率低。并不是绝对先调用优先级高的线程后调用优先级低的线程。
- 处于就绪状态的线程,会进入“就绪队列”等待JVM来挑选。
- 线程的优先级用数字表示,范围从1到10,一个线程缺省优先级是5.
- 使用下列方法获得或设置线程对象的优先级。
int getPriority(); void setPriority(int newPriority);
public class TestThread {
public static void mian(String[] args) {
Thread t1 = new Thread(new MyThread(), "t1");
Thread t2 = new Thread(new MyThread(), "t2");
t1.setPriority(1);
t2.setPriority(10);
t1.start();
t2.start();
}
}
class MyThread extends Thread {
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
线程同步
处理多线程问题时,多个线程访问一个对象,并且某些线程还想修改这个对象。这时候,我们就需要用到“线程同步”。线程同步起始就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕后,下一个线程再使用。
多线程操作同一个对象(未使用线程同步)
public class TestSync {
public static void main(String[] args) {
Account a1 = new Account(100, "高");
Drawing draw1 = new Drawing(80, a1);// 定义取钱线程对象;
Drawing draw2 = new Drawing(80, a1);// 定义取钱线程对象;
draw1.start(); // 你取钱
draw2.start(); // 你老婆取钱
}
}
/*
* 简单表示银行账户
*/
class Account {
int money;
String aname;
public Account(int money, String aname) {
super();
this.money = money;
this.aname = aname;
}
}
/**
* 模拟提款操作
*/
class Drawing extends Thread {
int drawingNum; // 取多少钱
Account account; // 要取钱的账户
int expenseTotal; // 总共取的钱数
public Drawing(int drawingNum, Account account) {
super();
this.drawingNum = drawingNum;
this.account = account;
}
@Override
public void run() {
if (account.money - drawingNum < 0) {
return;
}
try {
Thread.sleep(1000); // 判断完后阻塞。其他线程开始运行。
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money -= drawingNum;
expenseTotal += drawingNum;
System.out.println(this.getName() + "--账户余额:" + account.money);
System.out.println(this.getName() + "--总共取了:" + expenseTotal);
}
}
未使用线程同步的结果
实现线程同步
- 由于同一线程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突的问题。Java语言提供了专门机制解决这种冲突,有效避免了同一个数据对象被多个线程同时访问造成的这种问题。
- 由于我们可以通过private 关键字来保证数据对象只能被访问,所以我们只需针对方法提出一套机制,这套机制就是
synchronized
关键字,它包括两种用法:synchronized方法
和synchronized块
。
synchronized方法
- 通过在方法声明中加入synchronized 关键字来声明,如:
public synchronized void accessVal(int newVal);
- synchronized方法控制对“对象的类成员变量”的访问:每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占改锁,直到该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行的状态。
synchronized块
- synchronized方法的缺陷:若将一个大的方法声明为synchronized将会大大影响效率。
- Java为我们提供了更好的解决办法,那就是synchronized块。块可以让我们精确地控制到具体的“成员变量”,缩小同步的范围,提高效率。
- synchronized块语法为:
synchronized(syncObject){允许空值的代码}
public class TestSync {
public static void main(String[] args) {
Account a1 = new Account(100, "高");
Drawing draw1 = new Drawing(80, a1);
Drawing draw2 = new Drawing(80, a1);
draw1.start(); // 你取钱
draw2.start(); // 你老婆取钱
}
}
/*
* 简单表示银行账户
*/
class Account {
int money;
String aname;
public Account(int money, String aname) {
super();
this.money = money;
this.aname = aname;
}
}
/**
* 模拟提款操作
*
* @author Administrator
*
*/
class Drawing extends Thread {
int drawingNum; // 取多少钱
Account account; // 要取钱的账户
int expenseTotal; // 总共取的钱数
public Drawing(int drawingNum, Account account) {
super();
this.drawingNum = drawingNum;
this.account = account;
}
@Override
public void run() {
draw();
}
void draw() {
synchronized (account) {
// 这儿锁定的时account账户
if (account.money - drawingNum < 0) {
System.out.println(this.getName() + "取款,余额不足!");
return;
}
try {
Thread.sleep(1000); // 判断完后阻塞。其他线程开始运行。
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money -= drawingNum;
expenseTotal += drawingNum;
}
System.out.println(this.getName() + "--账户余额:" + account.money);
System.out.println(this.getName() + "--总共取了:" + expenseTotal);
}
}
- “synchronized(account)” 意味着线程需要获得account对象的“锁”才有资格运行同步块中的代码。Account对象的“锁”也称为“互斥锁”,在同一时刻只能被一个线程使用。
- A线程拥有锁,则可以调用“同步块”中的代码;B线程没有锁,则进入account对象的“锁池队列”等待,直到A线程使用完毕释放了account对象的锁,B线程得到锁才可以开始调用“同步块”中的代码。
死锁及解决方案
死锁
- 死锁是指,各个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能进程,而导致两个或多个线程都在等待对方释放资源,都停止执行的情形。
- 比如三个人吃饭,没两个人中间放着一种餐具,要每个人手里都拿到两个餐具才能吃饭,但是同时开始吃饭的话,就会造成,每人拿着一个餐具,都不能吃饭的结局。
class Lipstick {
// 口红类
}
class Mirror {
// 镜子类
}
class Makeup extends Thread {
// 化妆类继承了Thread类
int flag;
String girl;
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
@Override
public void run() {
doMakeup();
}
void doMakeup() {
if (flag == 0) {
synchronized (lipstick) {
// 需要拿到口红的锁
System.out.println(girl + "拿着口红!");
try {
Thread.sleep(1000);
} catch (InterruptedException e){
e.printStackTrace();
}
synchronized (mirror) {
// 需要得到镜子的锁
System.out.println(girl + "拿着镜子!");
}
}
} else {
synchronized (mirror) {
// 需要拿到口红的锁
System.out.println(girl + "拿着镜子!");
try {
Thread.sleep(2000);
} catch (InterruptedException e){
e.printStackTrace();
}
synchronized (lipstick) {
// 需要得到镜子的锁
System.out.println(girl + "拿着口红!");
}
}
}
}
}
public class TestDeadLock {
public static void main(String[] args) {
Makeup m1 = new Makeup();//大丫的化妆线程;
m1.girl = "大丫";
m1.flag = 0;
Makeup m2 = new Makeup();//小丫的化妆线程;
m2.girl = "小丫";
m2.flag = 1;
m1.start();
m2.start();
}
}
// 这样互相持锁,造成死锁
死锁的解决方法
死锁是由于“同步块需要同时持有两个对象锁造成”的,要解决这个问题,思路很简单,就是同一个代码块,不要同时持有两个对象的锁。
class Lipstick {//口红类
}
class Mirror {//镜子类
}
class Makeup extends Thread {//化妆类继承了Thread类
int flag;
String girl;
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
@Override
public void run() {
// TODO Auto-generated method stub
doMakeup();
}
void doMakeup() {
if (flag == 0) {
synchronized (lipstick) {
System.out.println(girl + "拿着口红!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (mirror) {
System.out.println(girl + "拿着镜子!");
}
} else {
synchronized (mirror) {
System.out.println(girl + "拿着镜子!");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (lipstick) {
System.out.println(girl + "拿着口红!");
}
}
}
}
public class TestDeadLock {
public static void main(String[] args) {
Makeup m1 = new Makeup();// 大丫的化妆线程;
m1.girl = "大丫";
m1.flag = 0;
Makeup m2 = new Makeup();// 小丫的化妆线程;
m2.girl = "小丫";
m2.flag = 1;
m1.start();
m2.start();
}
}
// 这儿没有锁的重复,也就是锁里边没有锁,所以不会造成互相持锁,造成死锁的这种情况。
线程并发协作
在多线程环境下,我们经常需要多个线程变并发和协作。这个时候,我们就需要了解一个重要的多线程并发协作模式“生产者/协作者模式”。
什么是生产者?
生产者指的是负责生产数据的模块(这里的模块可能是:对象,方法,线程,进程)。
什么是消费者?
消费者的是负责处理无数据的模块(这里的模块可能是:方法,对象,进程,线程)。
什么是缓冲区?
消费者不能直接使用生产者的数据,他们之间有个“缓冲区”。生产者将生产好的数据放入“缓冲区”,消费者从“缓冲区”拿要处理的数据。
实现线程的并发协作
- 有了缓冲区以后,生产者线程只需要往缓冲区里面防止数据,而不需要管消费者的情况;同样,消费者只需要从缓冲区拿数据处理即可,也不用管生产者生产的情况。这样,就从逻辑上实现了“生产者线程”和“消费者线程”的分离。
- 解耦了生产者和消费者:生产者不需要和消费者直接打交道。
- 解决了忙闲不均,提高效率:生产者生产数据慢时,缓冲区仍有数据,不影响消费者消费;消费者处理数据慢时,生产者仍然可以继续往缓冲区里边放置数据。
- 这样就会并发协作,你放一个,我拿一个,没了我就等待;放满了,你就等会儿放。
管程法
public class TestProduce {
public static void main(String[] args) {
SyncStack sStack = new SyncStack(); // 定义缓冲区对象;
Shengchan sc = new Shengchan(sStack); // 定义生产线程;
Xiaofei xf = new Xiaofei(sStack); // 定义消费线程;
sc.start();
xf.start();
}
}
class Mantou {// 馒头
int id;
Mantou(int id) {
this.id = id;
}
}
class SyncStack {// 缓冲区(相当于:馒头框)
int index = 0;
Mantou[] ms = new Mantou[10];
public synchronized void push(Mantou m) {
while(index == ms.length) {// 说明馒头筐满了
try {
this.wait();
/*
wait后,线程会将持有的锁释放,进入阻塞状态;
这样其他需要锁的线程就可以获得锁;
这里的含义是执行此方法的线程暂停,进入阻塞状态,等消费者消费了馒头后再生产。
*/
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notify();
/*
唤醒再当前对象等待池的第一个线程。
notifyAll叫醒所有在线程等待池种等待的所有线程。
如果不唤醒的话,以后这两个线程都会进入等待线程,没有人唤醒。
*/
ms[index] = m;
index++;
}
public synchronized Mantou pop() {
while(index == 0) {// 如果馒头是空的
try {
this.wait();
/*
如果馒头筐是空的,就暂停此消费线程(因为没有东西再可以消费了),等生产线程生产完再来消费。
*/
} catch (InterruptedExceptio e) {
e.printStackTrace();
}
}
this.notify();
index--;
return ms[index];
}
}
class Shengchan extends Thread {// 生产者线程
SyncStack ss = null;
public Shengchan(SyncStack ss) {
this.ss = ss;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("生产馒头:" + i);
Mantou m = new Mantou(i);
ss.push(m);
}
}
}
class Xiaofei extends Thread {// 消费者线程
SyncStack ss = null;
public Xiaofei(SyncStack ss) {
this.ss = ss;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
Mantou m = ss.pop();
System.out.println("消费馒头:" + i);
}
}
}
信号灯法
public class CoTest02 {
public static void main(String[] args) {
Tv tv = new Tv();
new Player(tv).start();
new Watcher(tv).start();
}
}
// 生产者 演员
class Player extends Thread{
Tv tv;
public Player(Tv tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (i %2 ==0) {
this.tv.play("奇葩说");
}else {
this.tv.play("太污了!");
}
}
}
}
// 消费者 观众
class Watcher extends Thread{
Tv tv;
public Watcher(Tv tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
tv.watch();
}
}
}
// 同一个资源 电视
class Tv{
String voice;
// 信号灯
// T表示演员表演,观众等待
// F表示观众观看,演员等待
boolean flag = true;
// 表演
public synchronized void play(String voice){
// 演员等待
if (!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("表演了:" + voice);
this.voice = voice;
this.notifyAll();
this.flag = !this.flag;
}
// 观看
public synchronized void watch(){
// 观众等待
if (flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("听到了:" + voice);
this.notifyAll();
this.flag = !this.flag;
}
}
线程并发协作总结
线程并发协作(也叫线程通信),通常用于生产者/消费者模式。
- 生产者和消费者共享一个资源,并且没有生产者和消费者之间的相互依赖(你干你的,我干我的),互为条件。
- 对于生产者,没有生产产品之前,消费者要进入等待状态。而生产了产品之后,又需要马上通知消费者消费。
- 对于消费者,在消费了后,要通知生产者已经消费结束,需要继续生产新产品以共消费。
- 在生产者消费者问题中,仅有synchronized 是不够的。
- synchronized 可阻止并发更新同一个共享资源,实现了同步;
- synchronized 不能用来实现不同线程之间的消息传递(通信)。
- 线程之间通过以下方法来进行消息传递(这些方法都是java.lang.Object类的方法,这些方法都只能在
同步方法或者同步代码块
中使用,否则抛出异常): | 方法名 | 作用 | | —- | —- | | final void wait() | 表示线程一直等待,直到得到其他线程通知 | | void wait(long timeout) | 线程等待指示毫秒参数的时间 | | final void wait(long timeout,int nanos) | 线程等待指示毫秒、微妙的时间 | | final void notify() | 唤醒一个处于等待状态的线程 | | final void notifyAll() | 唤醒同一个对象上所有调用的wait()方法的线程,优先级别搞得线程优先运行。 |
任务定时调度
通过Timer和TimerTask,我们可以实现定时启动某个线程
java.util.Timer
这种实现方式中,Timer类作用是类似闹钟的功能,也就是定时或者每隔一定的时间触发一次线程。其实。Timer来本身就是一个线程,只是这个线程使用来实现其他线程的。
java.util.TimerTask
TimerTask类是一个抽象类,该类实现了Runnable接口。 所以该类具备多线程的能力。在这种实现方式中,通过继承TimerTask使该类获得多线程的能力,将需要多线程执行的代码书写在run()方法内部,通过Timer类启动线程执行。
public class TestTimer {
public static void main(String[] args) {
Timer t1 = new Timer(); // 定义计时器
MyTask task1 = new MyTask(); // 定义任务;
//t1.schedule(task1,3000); // 3s后执行;
//t1.schedule(task1,5000,1000); // 5s以后每个1s执行一次!
GregorianCalendar calendar1 = new GregorianCalendar(2099,1,1,1,20,30);
t1.schedule(task1,calendar1.getTime()); // 指定时间定时执行。
}
}
class MyTask extends TimerTask {//自定义线程类继承TimerTask类;
@Override
public void run() {
for(int i = 0; i < 10; i++) {
System.out.println("任务1:" + i);
}
}
}
在实际使用时,一个Timer可以启动任意多个TimerTask实现的线程,但是多个线程之间会存在阻塞。所以如果多个线程之间需要完全独立的话,最好还是一个Timer启动一个TimerTask实现。
Lambda
- Lambda表达式也可称为闭包,它是推动java8发布的最重要的新特性。
- Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法)。
语法
(parameters) -> expression
或
(parameters) -> {statements;}
- Lambda重要特征
- 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
- 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
- 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
- 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定表达式返回了一个数值。
// 1. 不需要参数,返回值为5
() -> 5
// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x
// 3. 接受2个参数(数字),并返回他们的差值
(x, y) -> x - y
// 4. 接收2个int型整数,返回他们的和
(int x, int y) -> x + y
// 5. 接收一个String对象,并在控制台打印,不返回任何值(看起来像是void)
(String s) -> System.out.println(s)
public class Java8Tester {
public static void main(String[] args) {
Java8Tester tester = new Java8Tester();
// 类型声明
MathOperation addition = (int a, int b) -> a + b;
// 不用类型声明
MathOperation subtraction = (a, b) -> a - b;
// 大括号中返回语句
MathOperation multiplication = (int a, int b) -> {return a * b};
// 没有大括号及返回语句
MathOperation division = (int a, int b) -> a / b;
System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));
System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));
System.out.println("10 / 5 = " + tester.operate(10, 5, division));
// 不用括号
GreetingService greetService1 = message ->System.out.println("Hello " + message);
// 用括号
GreetingService greetService2 = (message) ->System.out.println("Hello " + message);
greetService1.sayMessage("Runoob");
greetService2.sayMessage("Google");
}
private int operate(int a, int b, MathOperation mathOperation) {
return mathOtheration.opteration(a, b);
}
}
interface MathOperation {
int operation(int a, int b);
}
interface GreetingService {
void sayMessage(String message);
}
- Lambda表达式注意:
- Lambda表达式主要用来定义行内执行的方法类型接口,例如:一个简单方法接口。
- Lambda表达式免去了使用匿名方法的麻烦,并且给予Java简单但是强大的函数化的编程能力。
- Lambda变量作用域
- Lambda表达式只能引用标记了final的外层局部变量,这就是说不能在lambda内部修改定义在域外的局部变量,否则会编译报错。
- 可以直接在 lambda 表达式中访问外层的局部变量
- lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)
- 在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。
静态代理(Proxy)
代理模式,简单来说,就是一个人专门负责干想干的事儿,其他事情不用你处理,会有人帮你处理
比如:你要结婚,你只需要happy,婚庆公司会帮你做好其他事物的。这就是代理模式。静态代理就是这一套流程是固定的,不会发生变化的,所以是静态代理。
public class StaticProxy {
public static void main(String[] args) {
new WeddingCompany(new You()).happyMarry();
//new Thread(线程对象).start();
}
}
interface Marry{
/**
* 结婚的接口
*/
void happyMarry();
}
class You implements Marry{
@Override
public void happyMarry() {
System.out.println("you and you girlfriend 。。。");
}
}
/**
* 代理角色
*/
class WeddingCompany implements Marry{
/**
* 真实角色
*/
private Marry target;
public WeddingCompany(Marry target) {
this.target = target;
}
@Override
public void happyMarry() {
this.ready();
this.target.happyMarry();
this.after();
}
private void ready(){
System.out.println("布置主卧!");
}
private void after(){
System.out.println("闹洞房!");
}
}