一、线程与进程的概念
1、进程
-
当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源。
而一个进程又是由多个线程所组成的。进程:可以理解为正在运行中的程序
2、线程
-
线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针、程序计数器等),但代码区是共享的, 即不同的线程可以执行同样的函数。
小结:进程是正在运行的程序,线程是基于进程的,也就是进程的进一步划分,线程结束了,进程不一定结束,但是如果进程结束了,运行在这个进程中的线程一定结束,也可以这么理解,一个线程必定有一个进程,而一个进程未必只有一个线程
3、两者之间的区别
二、单线程与多线程的区别
- 多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,
也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。 - 多线程的好处:
可以提高CPU的利用率。在多线程程序中,一个线程必须等待的时候,CPU可以运行其它的线程而不是等待,
这样就大大提高了程序的效率。
三、 线程之间执行顺序是无序的
- 俩个或者多个线程去抢占cpu资源,谁先抢到,谁先执行
四、实现线程的三种方式
1、实现Thread方法
- 启动线程:如果想正确的启动线程,是不能直接调用run()方法,应该调用从Thread类中继承而得到start()方法,才可以启动线程。
package all.demo.cn.多线程;
/**
* 第一中线程的写法
* 继承 Thread方法
* */
class MyThread extends Thread{
public MyThread() {
/**
* 构造器里面的调用 相当于是运行了main方法线程
* 因为一开始主线程是main方法中先调用出来,而MyThread方法还未创建成功
* */
Thread thread = Thread.currentThread();
System.out.print("构造器中线程Id:"+thread.getId()+"\t");
System.out.print("构造器中线程name:"+thread.getName()+"\n");
}
@Override
public void run() {
//调用run方法,实际就是在调用start方法中,调用了
Thread thread = Thread.currentThread();
System.out.print("第一种线程Id:"+thread.getId()+"\t");
System.out.print("第一种线程name:"+thread.getName()+"\n");
}
}
public class MainTest {
public static void main(String[] args) {
//第一种写法
MyThread myThread = new MyThread();
myThread.start();
myThread.run();
/**
* 第二种写法
* 实现Runnable
* */
Thread thread = new Thread(() -> {
System.out.print("第二种线程id:"+ Thread.currentThread().getId()+"\t");
System.out.print("第二种线程name:"+Thread.currentThread().getName()+"\n");
},"AAAA");
thread.start();
}
}
2、start方法与run方法的区别
- start()方法来启动线程,真正实现了多线程运行。这时无需等待run方法体代码执行完毕,可以直接继续执行下面的代码;通过调用Thread类的start()方法来启动一个线程, 这时此线程是处于就绪状态, 并没有运行。 然后通过此Thread类调用方法run()来完成其运行操作的, 这里方法run()称为线程体,它包含了要执行的这个线程的内容, Run方法运行结束, 此线程终止。然后CPU再调度其它线程。
- run()方法当作普通方法的方式调用。程序还是要顺序执行,要等待run方法体执行完毕后,才可继续执行下面的代码; 程序中只有主线程——这一个线程, 其程序执行路径还是只有一条, 这样就没有达到写线程的目的。
记住:多线程就是分时利用CPU,宏观上让所有线程一起执行 ,也叫并发
3、实现Runnable方法
class MyRunnable implements Runnable {
/**
* 子线程执行主体
* */
@Override
public void run() {
System.out.println("Runnable线程名字 = "+Thread.currentThread().getName()
+"Runnable线程ID = "+Thread.currentThread().getId());
}
}
public class MainTest {
public static void main(String[] args) {
//创建实现类对象
MyRunnable r=new MyRunnable();
//创建Thread类对象
Thread t1=new Thread(r);
//启动子线程
t1.start();
//局部内部类的写法:
class LocalMyRunnable implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("子线程名字="+Thread.currentThread().getName()+" 子线程ID="+Thread.currentThread().getId());
}
}
LocalMyRunnable localR=new LocalMyRunnable();
new Thread(localR).start();
//通过匿名内部类,创建子线程
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("子线程名字="+Thread.currentThread().getName()+" 子线程ID="+Thread.currentThread().getId());
}
}).start();
//lambda表达式创建子线程
new Thread(()->System.out.println("子线程名字="+Thread.currentThread().getName()+" 子线程ID="+Thread.currentThread().getId())).start();
}
}
4、线程运行原理
- 以前的main方法中都是执行压栈执行的,现在的线程不一样了,只要执行了start方法都会新开辟一个栈内存空间,所以新的线程与主线程的运行就有了随机性
5、Thread与Runnable接口两者的区别
6、实现Callable方法
- 使用的体系是
(1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
(2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
(3)使用FutureTask对象作为Thread对象的target创建并启动新线程。
(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
public class MyCallable implements Callable {
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 1; i < 101; i++) {
sum += i;
//获得当前执行线程的名称
System.out.println(Thread.currentThread().getName()+"---->"+sum);
}
return sum;
}
}
//测试类:
public class Test4 {
public static void main(String[] args) throws Exception {
//创建一个线程对象
MyCallable mc = new MyCallable();
//创建一个FutureTask对象
FutureTask<Integer> ft = new FutureTask<Integer>(mc);
//创建一个FutureTask对象
FutureTask<Integer> ft2 = new FutureTask<Integer>(mc);
//得到返回值
new Thread(ft,"线程A:").start();//启动线程
new Thread(ft2,"线程B:").start();
System.out.println(ft.get());
System.out.println(ft2.get());
}
}
7、三种线程创建的对比
采用实现Runnable、Callable接口的方式创见多线程时,
优势是:
1.线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。
2.在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
劣势是:
1.编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。
使用继承Thread类的方式创建多线程时
优势是:
3.编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。
劣势是:
- 线程类已经继承了Thread类,所以不能再继承其他父类。
推荐使用:Runnable Callable(有返回值时用他否则都用Runnable),
8、判断线程是否启动
public class Test7 {
public static void main(String[] args) {
RunnableDemo run = new RunnableDemo();
Thread th = new Thread(run);
boolean a1 = th.isAlive();//false
System.out.println(a1);
th.start();
boolean a2 = th.isAlive();//true
System.out.println(a2);
}
}
9、线程的强制运行
a、使用join()方法
- 使线程从运行态转换成就绪态
public class Test8 {
public static void main(String[] args) {
Mythread3 th = new Mythread3();
th.start();
for(int i=0;i<100;i++) {
if(i>10) {
try {
th.join();//当主线程循环大于10时,就让th执行完成后再执行
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}
class Mythread3 extends Thread{
@Override
public void run() {
for(int i=0;i<100;i++) {
System.out.println(this.getName()+"--->"+i);
}
}
}
10、线程的中断
a、调用interrupt()方法
- 方法可在需要中断的线程本身中调用,也可在其他线程中调用需要中断的线程对象的该方法。
public class Test11 {
public static void main(String[] args) {
Runnable1 run = new Runnable1();
Thread th = new Thread(run, "线程A");
th.start();// 启动线程
//让主线程休眠
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
th.interrupt();// 让线程终断
}
}
11、线程睡眠的方式
a、sleep方法
- sleep调用时不释放锁
- sleep 从运行态转换到阻塞态
- sleep最好不要作用于同步锁机制,因为此sleep在睡眠期间是 不会释放锁 ,导致其他线程也是执行不了
package all.demo.cn.多线程;
class Runnable1 implements Runnable {
@Override
public void run() {
System.out.println("1.进入run方法");
try {
Thread.sleep(3000);
System.out.println("2.已经休眠完成");
} catch (InterruptedException e) {
System.out.println("3.休眠被终止了");
return;
}
System.out.println("4.run方法正常结束");
}
}
class MySleepRunnable implements Runnable {
@Override
public void run() {
try {
for (int i = 0; i < 10 ; i++) {
System.out.println(Thread.currentThread().getId()+"+++++++>"+i);
if (i>5){
System.out.println(Thread.currentThread().getId()+"----->"+i);
Thread.sleep(5000);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class SleepTest {
public static void main(String[] args) {
MySleepRunnable mySleepRunnable = new MySleepRunnable();
new Thread(mySleepRunnable).start();
}
}
b、wait方法
package all.demo.cn.多线程;
public class WaitTest {
public static void main(String[] args) {
Object o = new Object();
new Thread(() -> {
for (int i = 0; i < 5 ; i++) {
System.out.println(Thread.currentThread().getName()+"i的值"+i);
synchronized (o){
try {
System.out.println("使用了wait方法");
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
},"AA").start();
}
}
12、yield方法
- yield方法使到当前线程暂停,让出cpu资源给其他的线程。但是和sleep()方法不同的是,它不会进入到阻塞状态,而是进入到就绪状态,让系统的线程调度器重新调度器重新调度一次
package yield方法;
//创建一个线程类
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("子线程
ID = "+Thread.currentThread().getId()+"i = "+i);
if (i == 30) {
System.out.println("子线程调用yield方法");
Thread.yield();
}
}
}
}
public class MainTest {
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start();
for (int j = 0; j < 100; j++) {
if (j == 60) {
//当前线程暂停(从运行态---->就绪态)
System.out.println("主线程调用yield方法");
Thread.yield();
}
System.out.println("主线程
ID = "+Thread.currentThread().getId()+"j = "+j);
}
}
}
12、如何结束线程
/**
* 用标志位结束线程
*/
public class Test6 {
public static void main(String[] args) {
MyRunnable6 mr = new MyRunnable6();
new Thread(mr, "线程1").start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 结束线程
mr.stop();
}
}
class MyRunnable6 implements Runnable {
private boolean flag = true;// 定义一个标志位
@Override
public void run() {
int i = 0;
while (flag) {
System.out.println(Thread.currentThread().getName() + "--->" + i++);
}
}
// 编写一个方法,可以用来结束线程
public void stop() {
this.flag = false;
}
}
package day806.cn.com;
class MyThread7 extends Thread {
//添加 一个终止线程的标记变量
public volatile boolean isFlag = false;
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("i=" + i);
//睡眠1秒,执行一次
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (!isFlag) {
break;
}
}
}
}
public class MainTest6 {
public static void main(String[] args) {
//创建线程类的对象
MyThread7 t = new MyThread7();
t.start();
try {
//主线程睡眠5秒
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.isFlag = true;
//不要使用stop方法,终止线程
// t.stop();
}
}
五、线程的生命周期
- 线程的状态及说明:
- 新建状态(New):新创建了一个线程对象
- 就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
- 运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
- 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
- 等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
- 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
- 其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
- 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
六、线程的优先级
1、简介
- 每个线程执行时都具有一定的优先级,优先级高的线程获得较多的执行机会,而优先级低的线程则获得较少的机会。
- 每个线程默认的优先级都与创建它的父线程的优先级相同,在默认情况下,main线程具有普通优先级,有main线程创建的子线程野具有普通优先级。
- Thread类提供了setPriority(int newPriority)、getPriority()方法来设置和返回指定线程的优先级,其setPriority()方法的参数是一个整数,范围在1~10之间
package all.demo.cn.多线程;
class MyRunnabless implements Runnable{
private String name;
public MyRunnabless(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 0; i <= 15; i++) {
int sum = i*10;
System.out.println(name+"跑了"+sum+"米");
if (sum==100){
break;
}
}
}
}
public class PriorityTest {
public static void main(String[] args) {
MyRunnabless rabbit = new MyRunnabless("兔子");
Thread thread = new Thread(rabbit);
thread.setPriority(Thread.MIN_PRIORITY);
thread.start();
MyRunnabless tortoise = new MyRunnabless("乌龟");
Thread thread1 = new Thread(tortoise);
thread1.setPriority(Thread.MAX_PRIORITY);
thread1.start();
}
}
七、后台线程
- 简介
- 有一种线程,它在后台运行,它的任务是为其他的线程提供服务,这
种线程被称为“后台线程”(Daemon Thread)。
- 有一种线程,它在后台运行,它的任务是为其他的线程提供服务,这
- 后台线程有个特征
- 后台线程有个特征,如果所有的前台线程都死亡,后台线程会自动
死亡。
- 后台线程有个特征,如果所有的前台线程都死亡,后台线程会自动
- 设置后台线程方法
- setDaemon(true)
package all.demo.cn.多线程;
class MyThreaddd extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
//睡眠1秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread id=" + Thread.currentThread().getId() + " i=" + i);
}
}
}
public class DaemonTest {
public static void main(String[] args) {
MyThreaddd myThreaddd = new MyThreaddd();
myThreaddd.setDaemon(true);
myThreaddd.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main thread end ");
}
}
八、线程安全问题
- 模拟一个银行业务,出现的线程不安全
package all.demo.cn.多线程;
class MyAccount{
private int balance;
private String account; //用户名
public MyAccount(int balance, String account) {
this.balance = balance;
this.account = account;
}
public int getBalance() {
return balance;
}
public void setBalance(int balance) {
this.balance = balance;
}
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
}
class MyThreadNoSyn extends Thread{
private MyAccount myAccount;
private int mun; //取出的金额
public MyThreadNoSyn(MyAccount myAccount, int mun) {
this.myAccount = myAccount;
this.mun = mun;
}
@Override
public void run() {
if (myAccount.getBalance()>=mun){
int s = myAccount.getBalance()-mun;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("用户总额:"+myAccount.getBalance()+"取款金额:"+mun+"余额:"+s);
myAccount.setBalance(s);
}else {
System.out.println("账号名 ="+myAccount.getBalance()+" 余额不足,无法正常取款");
}
}
}
public class NoSynchTest {
public static void main(String[] args) {
MyAccount myAccount = new MyAccount(1000,"AA");
MyThreadNoSyn myThreadNoSyn = new MyThreadNoSyn(myAccount,200);
MyThreadNoSyn myThreadNoSyn2 = new MyThreadNoSyn(myAccount,800);
myThreadNoSyn.start();
myThreadNoSyn2.start();
}
}
1、如何解决线程安全
a、使用同步代码块
- 为了防止多个线程同时访问和修改同一个对象,Java的多线程支持引入了同步监视器来解决这个问题,使用同步监视器的通用方法就是同步代码块。同步代码块的语法格式如下:
package all.demo.cn.多线程;
class MyAccount{
private int balance;
private String account; //用户名
public MyAccount(int balance, String account) {
this.balance = balance;
this.account = account;
}
public int getBalance() {
return balance;
}
public void setBalance(int balance) {
this.balance = balance;
}
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
}
class MyThreadNoSyn extends Thread{
private MyAccount myAccount;
private int mun; //取出的金额
public MyThreadNoSyn(MyAccount myAccount, int mun) {
this.myAccount = myAccount;
this.mun = mun;
}
@Override
public void run() {
//添加同步代码块
//mutex:同步监听器
/**
* 同步监听器设置的条件
* 1、必须是对象
* 2、共享资源
* */
synchronized (myAccount){
if (myAccount.getBalance()>=mun){
int s = myAccount.getBalance()-mun;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("用户总额:"+myAccount.getBalance()+"取款金额:"+mun+"余额:"+s);
myAccount.setBalance(s);
}else {
System.out.println("账号名 ="+myAccount.getBalance()+" 余额不足,无法正常取款");
}
}
}
}
public class NoSynchTest {
public static void main(String[] args) {
MyAccount myAccount = new MyAccount(1000,"AA");
MyThreadNoSyn myThreadNoSyn = new MyThreadNoSyn(myAccount,200);
MyThreadNoSyn myThreadNoSyn2 = new MyThreadNoSyn(myAccount,800);
myThreadNoSyn.start();
myThreadNoSyn2.start();
}
}
b、使用同步方法
- Java的多线程安全支持还提供了同步方法,同步方法就是使用synchronized关键字来修饰某个方法,则该方法称为同步方法。对于同步方法而言,无需显式指定同步监视器,同步方法的同步监视器是 this ,也就是改对象本身
package all.demo.cn.多线程;
class MyAccount {
private int balance;
private String account; //用户名
public MyAccount(int balance, String account) {
this.balance = balance;
this.account = account;
}
public int getBalance() {
return balance;
}
public void setBalance(int balance) {
this.balance = balance;
}
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
/**
* 定义一个同步方法
* */
public synchronized void drawMoney(int money) {
if(this.getBalance()>=money)
{
//局部变量(每个线程分配一份m的变量存储)
int m=this.getBalance()-money;
//睡眠1秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//account的balance属性才是共享变量值
this.setBalance(m);
System.out.println("账号名 ="+this.getAccount()+" 取款金额="+money+" 当前余额 ="+this.getBalance()+"线程id="+Thread.currentThread().getId()+" m="+m);
}else
{
System.out.println("账号名 ="+this.getAccount()+" 余额不足,无法正常取款");
}
}
}
class MyThreadNoSyn extends Thread {
private MyAccount myAccount;
private int mun; //取出的金额
public MyThreadNoSyn(MyAccount myAccount, int mun) {
this.myAccount = myAccount;
this.mun = mun;
}
@Override
public void run() {
/**
* 同步方法的线程安全
* */
myAccount.drawMoney(mun);
}
}
public class NoSynchTest {
public static void main(String[] args) {
MyAccount myAccount = new MyAccount(1000, "AA");
MyThreadNoSyn myThreadNoSyn = new MyThreadNoSyn(myAccount, 200);
MyThreadNoSyn myThreadNoSyn2 = new MyThreadNoSyn(myAccount, 800);
myThreadNoSyn.start();
myThreadNoSyn2.start();
}
}
c、使用显示锁
package all.demo.cn.多线程;
import java.util.concurrent.locks.ReentrantLock;
class MyAccount {
private int balance;
private String account; //用户名
public MyAccount(int balance, String account) {
this.balance = balance;
this.account = account;
}
public int getBalance() {
return balance;
}
public void setBalance(int balance) {
this.balance = balance;
}
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
/**
* 定义一个可重现锁
*/
private ReentrantLock lock = new ReentrantLock();
/**
* 定义一个普通的方法逻辑
*/
public void drawMoney(int money) {
lock.lock(); //加锁
if (this.getBalance() >= money) {
//局部变量(每个线程分配一份m的变量存储)
int m = this.getBalance() - money;
//睡眠1秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//account的balance属性才是共享变量值
this.setBalance(m);
System.out.println("账号名 =" + this.getAccount() + " 取款金额=" + money + " 当前余额 =" + this.getBalance() + "线程id=" + Thread.currentThread().getId() + " m=" + m);
} else {
System.out.println("账号名 =" + this.getAccount() + " 余额不足,无法正常取款");
}
lock.unlock();//解锁
}
}
class MyThreadNoSyn extends Thread {
private MyAccount myAccount;
private int mun; //取出的金额
public MyThreadNoSyn(MyAccount myAccount, int mun) {
this.myAccount = myAccount;
this.mun = mun;
}
@Override
public void run() {
/**
* 同步方法的线程安全
* */
myAccount.drawMoney(mun);
}
}
public class NoSynchTest {
public static void main(String[] args) {
MyAccount myAccount = new MyAccount(1000, "AA");
MyThreadNoSyn myThreadNoSyn = new MyThreadNoSyn(myAccount, 200);
MyThreadNoSyn myThreadNoSyn2 = new MyThreadNoSyn(myAccount, 800);
myThreadNoSyn.start();
myThreadNoSyn2.start();
}
}
九、死锁问题
- 所谓死锁,是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进
package all.demo.cn.多线程;
public class DieLock {
public static void main(String[] args) {
Object a = new Object();
Object b = new Object();
Thread aa = new Thread(() -> {
synchronized (a){
System.out.println("A线程运行中");
synchronized (b){
System.out.println("A线程结束");
}
}
});
Thread bb = new Thread(() -> {
synchronized (b){
System.out.println("B线程运行中");
synchronized (a){
System.out.println("B线程结束");
}
}
});
aa.start();
bb.start();
}
}
十、线程通信
-
当线程在系统内运行时,线程的调度具有一定的透明性,程序通常无法准确控制线程的轮换执行,但Java也提供了一些机制来保证线程协调运行
线程通信通常用于生产者/消费者模式
a、传统的线程通信
假设现在系统中有两个线程,这两个线程分别代表存款者和取钱者----现在有一种特殊的要求:存款者和取钱者不断地重复存款、取款动作,而且要求每当存款者将钱存入指定账户后,取钱者就立即取出该笔钱。不允许连续存款和取款。
为了实现这种功能,可以借助于Object类提供的wati()、notify()、notifyAll()三个方法。下面是关于这三个方法的解释:
- wait():导致当前线程等待,直到其他线程调用该同步监视器的notify()或notifyAll()来唤醒该线程(带参数则等待指定时间后自动苏醒)
- notify():唤醒在此同步监视器上等待的单个线程。如果所有线程都在此同步监视器上等待,则随机选择一个线程唤醒
- notifyAll():唤醒在此同步监视器上等待的所有线程
package all.demo.cn.多线程;
/**
*账户类
*/
class Account {
private double balance;//存款
private int id;//编号
private boolean flag=true;//判断账户是否有存款
public Account() {
}
public Account(double balance, int id) {
this.balance = balance;
this.id = id;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public double getBalance() {
return balance;
}
/**
* 取钱
*/
public synchronized void draw(int money){
if (flag){//账户有存款
System.out.println(DrawThread.currentThread().getName()+"取钱"+money);
//取钱
balance-=money;
System.out.println("账户余额为"+balance);
//账户无存款
flag=false;
//唤醒存钱线程
notifyAll();
}else{//账户没存款
//线程等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 存钱
*/
public synchronized void deposit(int money){
if (!flag){//账户没存款
System.out.println(DrawThread.currentThread().getName()+"存钱"+money);
//存钱
balance+=money;
System.out.println("账户余额为"+balance);
//账户有存款
flag=true;
//唤醒取款
notifyAll();
} else{//账户有存款
//线程等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 存钱线程
*/
class DepositThread extends Thread{
private Account account;//账户
private String name;//线程名
private int money;//存款金额
public DepositThread(String name, Account account, int money) {
super(name);
this.account = account;
this.money = money;
}
@Override
public void run() {
//存钱十次
for (int i = 0; i < 10; i++) {
account.deposit(money);
}
}
}
/**
* 取钱线程
*/
class DrawThread extends Thread{
private Account account;//账户
private String name;//线程名
private int money;//取款金额
public DrawThread(String name, Account account, int money) {
super(name);
this.account = account;
this.money = money;
}
@Override
public void run() {
//取款十次
for (int i = 0; i < 10; i++) {
account.draw(money);
}
}
}
/**
*测试类
*/
public class CommunTest {
public static void main(String[] args) {
//账户
Account account=new Account(1000,1);
//创建线程
DepositThread depositThread=new DepositThread("lsy",account,1000);
DrawThread drawThread=new DrawThread("yl",account,1000);
//启动线程
depositThread.start();
drawThread.start();
}
}
b、使用Condition控制线程通信
- Condition实例被绑定在一个Lock对象上。要获得特定Lock实例的Condition实例,调用Lock对象的newCondition()即可。Condition提供了如下三个方法:
- await():类似wait(),导致当前线程等待,直到其他线程调用该Condition的signal()或signalAll()来唤醒该线程
- signal():唤醒在此Lock对象上等待的单个线程。如果所有线程都在此Lock对象上等待,则随机选择一个线程唤醒
- signalAll():唤醒在此Lock对象上等待的所有线程
package Condition用法;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/*
* 定义一个账号类
* */
class Account {
private String name;
private double amount;
private boolean flag = true;
//定义一个可重现锁对象
ReentrantLock lock;
//定义一个Condition对象
Condition condition;
public Account() {
lock = new ReentrantLock();
condition = lock.newCondition();
}
public Account(String name, double amount) {
this.name = name;
this.amount = amount;
lock = new ReentrantLock();
condition = lock.newCondition();
}
/**
* @return the name
*/
public String getName() {
return name;
}
/**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}
/**
* @return the amount
*/
public double getAmount() {
return amount;
}
/**
* @param amount the amount to set
*/
public void setAmount(double amount) {
this.amount = amount;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "Account [name=" + name + ", amount=" + amount
+ "]";
}
/*
* 定义取款的方法
* */
public void draw(double money) {
//显式加锁
lock.lock();
if (flag) {
try {
condition.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} else {
setAmount(getAmount() - money);
System.out.println("取款成功 当前余额=" +
getAmount() + " 取款=" + money);
flag = true;
//唤醒存款线程,你该存款
condition.signal();
}
//释放锁
lock.unlock();
}
/*
* 定义存款的方法
* */
public void doposit(double money) {
//显式加锁
lock.lock();
//flag默认等于true
if (!flag) {
//存款处于等待,直到被取款线程唤醒
try {
condition.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} else {
setAmount(getAmount() + money);
System.out.println("存款成功 当前余额=" +
getAmount() + " 存款=" + money);
flag = false;
//唤醒取款的线程,你该取款
condition.signal();
}
//释放锁对象
lock.unlock();
}
}
/*
* 取款线程
*/
class DrawThread extends Thread {
private Account account;
private double money;
public DrawThread(Account account, double money) {
this.account = account;
this.money = money;
}
@Override
public void run() {
for (int i = 0; i < 11; i++) {
this.account.draw(money);
}
}
}
/*
* 存款线程
*/
class Dosipoit extends Thread {
private Account account;
private double money;
public Dosipoit(Account account, double money) {
this.account = account;
this.money = money;
}
@Override
public void run() {
for (int i = 0; i < 11; i++) {
this.account.doposit(money);
}
}
}
public class MainTest {
public static void main(String[] args) {
Account account = new Account("张三", 0);
Dosipoit d = new Dosipoit(account, 100);
DrawThread f = new DrawThread(account, 100);
d.start();
f.start();
}
}
十一、线程池的使用
- 线程池有四种创建方式
a、newFixedThreadPool
- 固定大小的线程池,可以指定线程池的大小,该线程池corePoolSize和maximumPoolSize相等,阻塞队列使用的是LinkedBlockingQueue,大小为整数最大值。该线程池中的线程数量始终不变,当有新任务提交时,线程池中有空闲线程则会立即执行,如果没有,则会暂存到阻塞队列。对于固定大小的线程池,不存在线程数量的变化。同时使用无界的LinkedBlockingQueue来存放执行的任务。当任务提交十分频繁的时候,LinkedBlockingQueue迅速增大,存在着耗尽系统资源的问题。而且在线程池空闲时,即线程池中没有可运行任务时,它也不会释放工作线程,还会占用一定的系统资源,需要shutdown。
public class FixPoolDemo {
private static Runnable getThread(final int i) {
return new Runnable() {
@Override
public void run() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
};
}
public static void main(String args[]) {
ExecutorService fixPool = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
fixPool.execute(getThread(i));
}
fixPool.shutdown();
}
}
b、newSingleThreadExecutor
- 单个线程线程池,只有一个线程的线程池,阻塞队列使用的是LinkedBlockingQueue,若有多余的任务提交到线程池中,则会被暂存到阻塞队列,待空闲时再去执行。按照先入先出的顺序执行任务。
public class SingPoolDemo {
private static Runnable getThread(final int i){
return new Runnable() {
@Override
public void run() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
};
}
public static void main(String args[]) throws InterruptedException {
ExecutorService singPool = Executors.newSingleThreadExecutor();
for (int i=0;i<10;i++){
singPool.execute(getThread(i));
}
singPool.shutdown();
}
c、newCachedThreadPool
- 缓存线程池,缓存的线程默认存活60秒。线程的核心池corePoolSize大小为0,核心池最大为Integer.MAX_VALUE,阻塞队列使用的是SynchronousQueue。是一个直接提交的阻塞队列, 他总会迫使线程池增加新的线程去执行新的任务。在没有任务执行时,当线程的空闲时间超过keepAliveTime(60秒),则工作线程将会终止被回收,当提交新任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销。如果同时又大量任务被提交,而且任务执行的时间不是特别快,那么线程池便会新增出等量的线程池处理任务,这很可能会很快耗尽系统的资源。
public class CachePool {
private static Runnable getThread(final int i){
return new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
}catch (Exception e){
}
System.out.println(i);
}
};
}
public static void main(String args[]){
ExecutorService cachePool = Executors.newCachedThreadPool();
for (int i=1;i<=10;i++){
cachePool.execute(getThread(i));
}
}
}
这里没用调用shutDown方法,这里可以发现过60秒之后,会自动释放资源
d、newScheduledThreadPool
-
定时线程池,该线程池可用于周期性地去执行任务,通常用于周期性的同步数据。
scheduleAtFixedRate:是以固定的频率去执行任务,周期是指每次执行任务成功执行之间的间隔。
schedultWithFixedDelay:是以固定的延时去执行任务,延时是指上一次执行成功之后和下一次开始执行的之前的时间。
public class ScheduledExecutorServiceDemo {
public static void main(String args[]) {
ScheduledExecutorService ses = Executors.newScheduledThreadPool(10);
ses.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(4000);
System.out.println(Thread.currentThread().getId() + "执行了");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, 0, 2, TimeUnit.SECONDS);
}
}
十二、ThreadLocal类应用
- ThreadLocal为解决多线程程序的并发问题提供了一种新的思路,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
class MyRunnable implements Runnable {
private int i = 0; //ThreadLocal默认初始化数据值0
ThreadLocal<Integer> threadId = new ThreadLocal<Integer> () {
@Override
protected Integer initialValue() {
return 0;
}
};
@Override public void run() {
for (int j = 0; j < 10; j++) {
//每个线程都有一份threadId变量的副本
//针对副本+1操作
threadId.set(threadId.get()+1);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
//针对副本-1操作
threadId.set(threadId.get()-1);
System.out.println("thread id=" +Thread.currentThread().getId() + " i=" + threadId.get());
}
}
}
public class MainTest {
public static void main(String[] args) {
MyRunnable r = new MyRunnable();
// 创建线程一
new Thread(r).start();
// 创建线程二
new Thread(r).start();
}
}