Java多线程
作者:Memory Wechat/QQ:574373426 学编程找星哥
1. 进程线程
1.1 什么是进程
正在进行中的程序,是系统进行资源分配的基本单位,目前操作系统都是支持多进程,可以同时执行多个进程,通过进程ID区分
进程是操作系统中运行的一个任务(一个应用程序运行在一个进程中)
进程是一块包含了某些资源的内存区域,操作系统利用进程把他们的工作划分为一些功能单元
进程中包含的一个或多个执行单元称为线程,进程还拥有一个私有的虚拟地址空间,该空间只能被他所包含的线程访问
线程只能属于一个进程并且它只能访问该进程所拥有的资源,当操作系统创建一个线程后,该进程会自动申请一个主线程
一个进程至少包含一个线程
1.2 什么是线程
一个线程是进程的一个顺序执行单元,也是CPU的基本调度单位
同类的多个线程共享一块内存空间和一组系统资源,线程本身有一个供程序执行时的堆栈
一个进程中可以包含多个线程
线程通常用于在一个程序中需要同步完成的多个任务情况,我们可以将每个任务定义为一个线程,使得他们统一 一起工作,同时执行,称为多线程
也可以使用单线程完成,但是多线程可以更快,比如下载文件
例如:
迅雷就是一个进程,当中多个下载任务就是多个线程
java虚拟机是一个进程,当中默认包含主线程(main),可以通过代码创建多个独立线程,与main并发执行
1.3 并发
多个线程“同时运行”只是我们感官上的一种表现,事实上线程是并发运行的
系统将时间划分为很多时间片段(时间片),尽可能均匀的分配给每一个线程,获取时间片的线程被CPU调用运行,其他线程全部等待,微观上走走停停,宏观上都在运行,这种现象叫并发,并发不是绝对意义上的同时运行。
以上为单核的情况,目前CPU都是多核的,能实现真正的并发
1.4 进程和线程的区别
- 进程是操作系统资源分配的基本单位,而线程是cpu的基本调度单位
- 一个程序运行后至少有一个进程
- 一个进程可以包含多个线程,但是至少需要有一个线程,否则这个进程是没有意义的
- 进程间不能共享数据段地址,但同进程的线程之间是可以的
2. 线程的组成
2.1 线程的组成
任何一个线程都具有基本的组成部分
- CPU时间片:操作系统(OS)会为每个线程分配执行时间
- 运行数据
- 堆空间:存储线程需使用的对象,多个线程可以共享堆中的对象
- 栈空间:存储线程需要使用的局部变量,每个线程都拥有独立的栈
- 线程的逻辑代码
2.2 线程特点
- 线程抢占式执行 (100个任务分给5个人去执行 有的会分配有的不会分配效率低,可能一个员工执行100个任务)
- 效率高
- 可防止单一线程长时间独占CPU
- 单核CPU中,宏观上同时执行,微观上顺序执行
2.3 创建线程
创建线程的三种方式
- 继承Thread类,重写Run方法
- 实现Runnable接口
- 实现Callable接口 (jdk1.5以后新加的接口)
2.4 继承Thread类
2.4.1 创建和启动线程
案例1:主线程和子线程抢占时间片执行for循环
package com.gx;
/**
* 线程类
*/
public class MyThread extends Thread{
@Override
public void run() {
for (int i=0;i<100;i++){
System.out.println("子线程......"+i);
}
}
}
package com.gx;
public class Test {
public static void main(String[] args) {
//1.创建线程对象
//main方法是jvm的主线程,而我们创建的线程统称为子线程
MyThread myThread = new MyThread();
//2.启动线程
myThread.start();
//myThread.run(); 如果调用run方法则还是主线程去执行某一个类里的方法,就不是子线程执行
//3.主线程中创建一个for用来对比
for (int i=0;i<50;i++){
System.out.println("主线程======"+i);
}
}
}
案例2:多个个子线程同时执行的时候,不好区分到底是哪个子线程执行的,我们可以获取线程名
public class Test {
public static void main(String[] args) {
//1.创建线程对象
//main方法是jvm的主线程,而我们创建的线程统称为子线程
//创建2个子线程
MyThread myThread = new MyThread();
myThread.start();
MyThread myThread1 = new MyThread();
myThread1.start();
//myThread.run(); 如果调用run方法则还是主线程去执行某一个类里的方法,就不是子线程执行
//3.主线程中创建一个for用来对比
for (int i=0;i<50;i++){
System.out.println("主线程======"+i);
}
}
}
2.4.2 获取和修改线程名称
获取线程ID | 获取线程名称 | 备注 |
---|---|---|
this.getId() | this.getName() | 当前的方法是继承的Thread类里的方法,有局限性,只能在继承的方式中使用 |
Thread.currentThread().getId() | Thread.currentThread().getName() | |
package com.gx;
/**
* 线程类
*/
public class MyThread extends Thread{
@Override
public void run() {
for (int i=0;i<100;i++){
//第一种方式 this.getId() this.getName()
//System.out.println("线程id:"+this.getId()+"线程名称:"+this.getName()+"子线程......"+i);
//第二种方式 Thread.currentThread() 获取当前线程
System.out.println(
"线程id:"+Thread.currentThread().getId()+
"线程名称:"+Thread.currentThread().getName()+
"子线程......"+i
);
}
}
}
修改线程名称 |
---|
线程对象.setName() |
线程子类的构造方法赋值 public MyThread(String name){ super(name); } |
package com.gx;
public class Test {
public static void main(String[] args) {
//1.创建线程对象
//main方法是jvm的主线程,而我们创建的线程统称为子线程
//创建2个子线程
MyThread myThread = new MyThread("我的子线程1");
//修改线程名称
//myThread.setName("我的子线程1");
myThread.start();
MyThread myThread1 = new MyThread("我的子线程2");
//myThread1.setName("我的子线程2");
myThread1.start();
//myThread.run(); 如果调用run方法则还是主线程去执行某一个类里的方法,就不是子线程执行
//3.主线程中创建一个for用来对比
for (int i=0;i<50;i++){
System.out.println("主线程======"+i);
}
}
}
package com.gx;
/**
* 线程类
*/
public class MyThread extends Thread{
public MyThread(){
}
public MyThread(String name){
super(name);
}
@Override
public void run() {
for (int i=0;i<100;i++){
//第一种方式 this.getId() this.getName()
//System.out.println("线程id:"+this.getId()+"线程名称:"+this.getName()+"子线程......"+i);
//第二种方式 Thread.currentThread() 获取当前线程
System.out.println(
"线程id:"+Thread.currentThread().getId()+
"线程名称:"+Thread.currentThread().getName()+
"子线程......"+i
);
}
}
}
2.4.3 案例
使用继承Thread类实现4个窗口各卖100张票
package com.gx;
/**
* 卖票窗口类(线程类)
*/
public class TickeWin extends Thread {
public TickeWin(){}
public TickeWin(String name){
super(name);
}
private int ticket =100; //票
@Override
public void run() {
//卖票功能
while (true){
if (ticket<=0){
break;
}
System.out.println(Thread.currentThread().getName()+"卖了第"+ticket+"张票");
ticket--;
}
}
}
package com.gx;
public class TestWin {
public static void main(String[] args) {
//创建四个窗口
TickeWin w1 = new TickeWin("窗口1");
TickeWin w2 = new TickeWin("窗口2");
TickeWin w3 = new TickeWin("窗口3");
TickeWin w4 = new TickeWin("窗口4");
//启动线程
w1.start();
w2.start();
w3.start();
w4.start();
}
}
2.5 实现Runnable接口
package com.gx;
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+"......"+i);
}
}
}
package com.gx;
public class TestRunnable {
public static void main(String[] args) {
//1.创建MyRunnable对象,表示线程要执行的功能
MyRunnable runnable = new MyRunnable();
//2.创建线程对象
Thread thread = new Thread(runnable,"我的线程1");
//3.启动线程
thread.start();
//主线程
for(int i=0;i<100;i++){
System.out.println("main......"+i);
}
}
}
补充:采用匿名内部类
package com.gx;
public class TestRunnable {
public static void main(String[] args) {
//创建可运行对象
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+"......"+i);
}
}
};
Thread thread = new Thread(runnable,"我的线程2");
thread.start();
for(int i=0;i<100;i++){
System.out.println("main......"+i);
}
}
}
2.5.1 案例
#实现四个窗口共卖100张票
package com.gx.d2;
/**
* 票类(共享资源)
*/
public class Ticket implements Runnable{
private int ticket = 100;//100张票
@Override
public void run() {
while (true){
if (ticket<=0){
break;
}
System.out.println(Thread.currentThread().getName()+" 卖了第"+ticket+"张票");
ticket--;
}
}
}
package com.gx.d2;
public class TestTicket {
public static void main(String[] args) {
//1.创建票对象
Ticket ticket = new Ticket();
//2.创建线程对象
Thread w1 = new Thread(ticket,"窗口1");
Thread w2 = new Thread(ticket,"窗口2");
Thread w3 = new Thread(ticket,"窗口3");
Thread w4 = new Thread(ticket,"窗口4");
//3.启动线程
w1.start();
w2.start();
w3.start();
w4.start();
}
}
#此时多个窗口会卖出同一张票,后面的课程中解决
2.5.2 案例
#你和你女朋友共用一张银行卡,你向卡中存钱,你女朋友从卡里取钱
package com.gx.d2;
/**
* 银行卡
*/
public class BankCard {
private double money;//余额
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
}
package com.gx.d2;
/**
* 存钱
*/
public class AddMoney implements Runnable{
private BankCard card;
public AddMoney(BankCard card){
this.card=card;
}
@Override
public void run() {
for (int i=0;i<10;i++){
card.setMoney(card.getMoney()+1000);
System.out.println(Thread.currentThread().getName()+" 存了1000,余额是"+card.getMoney());
}
}
}
package com.gx.d2;
/**
* 取钱
*/
public class SubMoney implements Runnable{
private BankCard card;
public SubMoney(BankCard card) {
this.card = card;
}
@Override
public void run() {
for (int i=0;i<10;i++){
if (card.getMoney()>=1000){
card.setMoney(card.getMoney()-1000);
System.out.println(Thread.currentThread().getName()+" 取了1000,余额是"+card.getMoney());
}else {
System.out.println("余额不足,请赶快存取");
i--;
}
}
}
}
package com.gx.d2;
public class TestBankCard {
public static void main(String[] args) {
//1.创建一张银行卡
BankCard card = new BankCard();
//2.创建存钱取钱
AddMoney add = new AddMoney(card);
SubMoney sub = new SubMoney(card);
//3.创建两个线程
Thread nan = new Thread(add,"男朋友");
Thread nv = new Thread(sub,"女朋友");
//4.启动线程
nan.start();
nv.start();
}
}
#匿名内部类方式
package com.gx.d2;
public class TestBankCard2 {
public static void main(String[] args) {
BankCard card = new BankCard();
//存钱
Runnable add = new Runnable() {
@Override
public void run() {
for (int i=0;i<10;i++){
card.setMoney(card.getMoney()+1000);
System.out.println(Thread.currentThread().getName()+" 存了1000,余额是"+card.getMoney());
}
}
};
//取钱
Runnable sub = new Runnable() {
@Override
public void run() {
for (int i=0;i<10;i++){
if (card.getMoney()>=1000){
card.setMoney(card.getMoney()-1000);
System.out.println(Thread.currentThread().getName()+" 取了1000,余额是"+card.getMoney());
}else {
System.out.println("余额不足,请赶快存取");
i--;
}
}
}
};
//创建线程对象并启动
new Thread(add,"那朋友").start();
new Thread(sub,"女朋友").start();
}
}
#上述两个案例中存在线程安全同步问题,后期学了相关知识点,可以解决
3. 线程的状态
3.1 基本状态
3.2 线程常见方法
休眠
- public static void sleep (long millis)
- 当前线程主动休眠millis毫秒
package com.gx.d3;
public class SleepThread extends Thread{
@Override
public void run() {
for (int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"......"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
SleepThread s1 = new SleepThread();
s1.start();
SleepThread s2 = new SleepThread();
s2.start();
}
}
package com.gx.d3;
import java.text.SimpleDateFormat;
import java.util.Date;
public class TestSleep {
public static void main(String[] args) {
Thread thread = new Thread(){
@Override
public void run() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
while (true){
System.out.println(sdf.format(new Date()));
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
thread.start();
}
}
放弃
public static void yield()
当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片
package com.gx.d3;
public class YieldThread extends Thread{
@Override
public void run() {
for (int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"......"+i);
//主动放弃cup
Thread.yield();
}
}
//测试
public static void main(String[] args) {
YieldThread y1 = new YieldThread();
YieldThread y2 = new YieldThread();
y1.start();
y2.start();
}
}
package com.gx.d3;
public class YildTest {
public static void main(String[] args) {
Thread t1 = new Thread(){
@Override
public void run() {
System.out.println("t1开始跑步");
for (int i=1;i<=400;i++){
System.out.println("t1:跑了"+i+"米");
if(i==200){
Thread.yield();
}
}
System.out.println("t1跑完了");
}
};
Thread t2 = new Thread(){
@Override
public void run() {
System.out.println("t2开始跑步");
for(int i=1;i<=400;i++){
System.out.println("t2:跑了"+i+"米");
}
System.out.println("t2跑完了");
}
};
t1.start();
t2.start();
}
}
加入
public final void join()
允许其他线程加入到当前线程中,造成当前线程阻塞,常用于等待当前线程结束
package com.gx.d3;
public class JoinThread extends Thread{
@Override
public void run() {
for(int i=0;i<30;i++){
System.out.println(Thread.currentThread().getName()+"......"+i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
JoinThread j1 = new JoinThread();
j1.start();
//j1.join();//加入当前线程(main),并阻塞当前线程,直到加入线程执行完毕
for (int i=0;i<20;i++){
System.out.println(Thread.currentThread().getName()+"============"+i);
Thread.sleep(100);
}
}
}
package com.gx.d3;
public class JoinTest {
public static void main(String[] args) {
Thread t1 = new Thread() {
@Override
public void run() {
for (int i = 0; i <= 10; i++) {
System.out.println("t1:正在下载图片:" + i * 10 + "%");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t1:图片下载完毕");
}
};
Thread t2 = new Thread() {
@Override
public void run() {
System.out.println("t2:等待t1图片下载完毕");
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t2:显示图片");
}
};
t1.start();
t2.start();
}
}
线程优先级
- 线程对象.setPriority()
- 线程优先级为 1-10,1最低,10最高,默认是5,优先级越高,表示获取CPU机会越多
- 线程提供了3个常量来表示最低(Thread.MIN_PRIORITY),最高(Thread.MAX_PRIORITY),默认(Thread.NORM_PRIORITY)
package com.gx.d4;
/**
* 线程优先级
*/
public class PriorityThread extends Thread{
@Override
public void run() {
for (int i=0;i<50;i++){
System.out.println(Thread.currentThread().getName()+"=========="+i);
}
}
public static void main(String[] args) {
PriorityThread p1 = new PriorityThread();
p1.setName("p1");
PriorityThread p2 = new PriorityThread();
p2.setName("p2");
PriorityThread p3 = new PriorityThread();
p3.setName("p3");
//设置优先级
p1.setPriority(1);
p3.setPriority(10);
//启动 如果没有设置优先级 大概率p3再最后执行完
p1.start();
p2.start();
p3.start();
}
}
守护线程
- 线程对象.setDaemon(true) 设置为守护线程
- 线程有两类,用户线程(前台线程),守护线程(后台线程)
- 如果程序中所有前台线程都执行完毕了,后台线程会自动结束
- 垃圾回收器线程属于守护线程
public class DeamonThread extends Thread{
@Override
public void run() {
for (int i=0;i<50;i++){
System.out.println(Thread.currentThread().getName()+"---------"+i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
//创建线程(默认为前台线程)
DeamonThread d1 = new DeamonThread();
//设置为守护线程 当主线程接收后 守护线程也会结束
d1.setDaemon(true);
d1.start();
//主线程也是前台线程
for (int i=0;i<10;i++){
System.out.println("主线程:----------"+i);
}
}
}
3.3 线程的状态(等待)
4. 线程安全
多线程安全问题
- 当多线程并发访问临界资源,如果破坏原子操作,可能会造成数据不一致
- 临界资源:共享资源,一次仅允许一个线程使用,才可保证其正确性
- 原子操作:不可封额的多步操作,被视作一个整体,其顺序和步骤不可打乱或缺省
public class ThreadSafe {
private static int index=0;
public static void main(String[] args) throws InterruptedException {
//创建数组 共享资源
String[] str = new String[5];
//创建两个操作
Runnable r1 = new Runnable() {
@Override
public void run() {
str[index]="hello";
index++;
}
};
Runnable r2 = new Runnable() {
@Override
public void run() {
str[index]="world";
index++;
}
};
//创建两个线程对象
Thread a = new Thread(r1,"A");
Thread b = new Thread(r2,"B");
a.start();
b.start();
a.join();//加入线程
b.join();//加入线程
//多运行几次发现问题 只放了一个值
System.out.println(Arrays.toString(str));
}
}
思考:在程序应用中,如何保证线程的安全性
4.1 同步方式(1)
同步代码块 synchronized 厕所上锁,其他等待
同步代码块中传的参数只需要传一个唯一的引用当锁即可,也就是任何物体都可以作为一把锁,保证唯一
小明,小红他们来到工作的地方。获取锁的地方就是路边的一棵树:注意现在路边只有这一棵树。小红抢先一步将树上的锁拿走了。然后小红工作去了(她的工作 不用和树有关系,她可以砍树,也可以去路边扫地 )。小明,小芳等其他人,因为没地方获取锁(只有一棵树 并且树的锁 已经被小红拿走了)。所以只能在原地等待 ,小红工作做完了 把锁还给树,小明,小芳才有机会进行工作。
- 每个对象都有一个互斥锁标记(任何对象都可以用来做一个锁),用来分配给线程
- 只有拥有对象互斥锁标记的线程,才能进入对该对象加锁的同步代码块
- 线程退出同步代码块时,会释放相应的互斥锁标记
synchronized(临界资源对象){ //对临界资源对象加锁
//代码(原子操作)
}
public class ThreadSafe {
private static int index=0;
public static void main(String[] args) throws InterruptedException {
//创建数组 共享资源
String[] str = new String[5];
//创建两个操作
Runnable r1 = new Runnable() {
@Override
public void run() {
synchronized (str){
str[index]="hello";
index++;
}
}
};
Runnable r2 = new Runnable() {
@Override
public void run() {
synchronized (str){
str[index]="world";
index++;
}
}
};
//创建两个线程对象
Thread a = new Thread(r1,"A");
Thread b = new Thread(r2,"B");
a.start();
b.start();
a.join();//加入线程
b.join();//加入线程
System.out.println(Arrays.toString(str));
}
}
案例:卖票
/**
* 票类(共享资源)
*/
public class Ticket implements Runnable{
private int ticket = 100;//100张票
//创建锁
private Object obj = new Object();
@Override
public void run() {
while (true){
//这里必须传一个引用类型的值,此时ticket不可以
//1.可以自己创建一个对象当锁 obj此时内存中只有一份
//2.也可以使用this this为当前对象也就是Ticket对象,内存中也只有一份
//3.也可以讲ticket声明为Integer类型
//4.这里只需要传一个唯一的引用当锁即可,也就是任何物体都可以作为一把锁,保证唯一
synchronized (obj){
if (ticket<=0){
break;
}
System.out.println(Thread.currentThread().getName()+" 卖了第"+ticket+"张票");
ticket--;
}
}
}
public static void main(String[] args) {
//1.创建票对象
Ticket ticket = new Ticket();
//2.创建线程对象
Thread w1 = new Thread(ticket,"窗口1");
Thread w2 = new Thread(ticket,"窗口2");
Thread w3 = new Thread(ticket,"窗口3");
Thread w4 = new Thread(ticket,"窗口4");
//3.启动线程
w1.start();
w2.start();
w3.start();
w4.start();
}
}
案例:银行取钱存钱
public class BankCard {
private double money;
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
public static void main(String[] args) {
//创建银行卡
BankCard card = new BankCard();
//创建两个操作
Runnable add = new Runnable() {
@Override
public void run() {
for(int i=0;i<10;i++){
synchronized (card){
card.setMoney(card.getMoney()+1000);
System.out.println(
Thread.currentThread().getName()+"存了1000,余额为"+card.getMoney());
}
}
}
};
Runnable sub = new Runnable() {
@Override
public void run() {
for(int i=0;i<10;i++){
synchronized (card){
if (card.getMoney()>=1000){
card.setMoney(card.getMoney()-1000);
System.out.println(
Thread.currentThread().getName()+"取了1000,余额为"+card.getMoney());
}else {
System.out.println("余额不足");
i--;
}
}
}
}
};
//创建两个线程对象
Thread t1 = new Thread(add,"男");
Thread t2 = new Thread(sub,"女");
t1.start();
t2.start();
}
}
4.2 线程的状态(阻塞)
打开Thread源码查看 State
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}
4.3 同步方式(2)
同步方法
- 只有拥有对象互斥锁标记的线程,才能进入该对象加索的同步方法中
- 线程退出同步方法时,会释放相应 的互斥锁标记
synchronized 返回值类型 方法名称(形参列表){ //对当前对象this加锁
//代码(原子操作)
}
案例:卖票
/**
* 票类(共享资源)
*/
public class Ticket implements Runnable{
private int ticket = 100;//100张票
@Override
public void run() {
while (true){
if(!sale()){
break;
}
}
}
//将卖票的功能提取到方法中
public synchronized boolean sale(){ //锁为this(ticket) 如果为静态方法,锁就为当前的类Ticket.class
if (ticket<=0){
return false;
}
System.out.println(Thread.currentThread().getName()+"卖了第"+ticket+"张票");
ticket--;
return true;
}
public static void main(String[] args) {
//1.创建票对象
Ticket ticket = new Ticket();
//2.创建线程对象
Thread w1 = new Thread(ticket,"窗口1");
Thread w2 = new Thread(ticket,"窗口2");
Thread w3 = new Thread(ticket,"窗口3");
Thread w4 = new Thread(ticket,"窗口4");
//3.启动线程
w1.start();
w2.start();
w3.start();
w4.start();
}
}
4.4 同步规则
注意
- 只有在调用包含同步代码块的方法,或者同步方法时,才需要对象的锁标记
- 如调用不包含同步代码块的方法,或普通方法时,则不需要锁标记,可以直接调用
已知JDK中线程安全的类
- StringBuffer
- Vector
- Hashtable
- 以上类中的公开方法,均为synchronized修饰的同步方法
4.5 经典问题
死锁
所谓死锁,是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。
当第一个线程拥有A对象锁标记,并等待B对象锁标记。同时第二个线程拥有B对象锁标记,并且等待A对象锁标记时,产生死锁
一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的锁的标记,由此可能造成死锁
案例:两根筷子,男女抢
/**
* 创建两个锁对象
*/
public class MyLock {
//两个锁(两根筷子)
public static Object a = new Object();
public static Object b = new Object();
}
public class Boy extends Thread{
@Override
public void run() {
synchronized (MyLock.a){
System.out.println("男孩拿到了a");
synchronized (MyLock.b){
System.out.println("男孩拿到了b");
System.out.println("男孩可以吃东西了");
}
}
}
}
public class Girl extends Thread{
@Override
public void run() {
synchronized (MyLock.b){
System.out.println("女孩拿到了b");
synchronized (MyLock.a){
System.out.println("女孩拿到了a");
System.out.println("女孩可以吃东西了");
}
}
}
}
public class TestDeadLock {
public static void main(String[] args) throws InterruptedException {
Boy boy = new Boy();
Girl girl = new Girl();
//此时启动 形成死锁
//boy.start();
//girl.start();
boy.start();
//主线程休眠 先让boy执行 再让girl执行,这里指的是main线程休眠
Thread.sleep(100);
girl.start();
}
}
4.6 线程通信
等待
- public final void wait()
- public final void wait(long timeout)
- 必须在对obj加锁的同步代码块中,在一个线程中,调用obj.wait()时,此线程会释放其他拥有的所有锁标记,同时此线程阻塞在o的等待队列中。释放锁,进入等待队列
通知
- public final void notify()
- public final void notifyAll()
案例:存钱取钱
实现 存一次 取一次 依次交替进行
public class BankCard {
//余额
private double money;
//开关 标记
private boolean falg=false;//true表示有钱可以取钱,false没钱可以存钱
//存钱
public synchronized void save(double m){
if (falg){ //有钱
try {
this.wait();//进入等待队列,同时释放锁和cup
} catch (InterruptedException e) {
e.printStackTrace();
}
}
money=money+m;
System.out.println(Thread.currentThread().getName()+"存了"+m+"余额是"+money);
//修改标记
falg=true;
//唤醒取钱线程
this.notify();
}
//取钱
public synchronized void take(double m){
if (!falg){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
money=money-m;
System.out.println(Thread.currentThread().getName()+"取了"+m+"余额是"+money);
//修改标记
falg=false;
//唤醒存钱线程
this.notify();
}
}
public class AddMoney implements Runnable{
private BankCard card;
public AddMoney(BankCard card) {
this.card = card;
}
@Override
public void run() {
for (int i=0;i<10;i++){
card.save(1000);
}
}
}
public class SubMoney implements Runnable{
private BankCard card;
public SubMoney(BankCard card) {
this.card = card;
}
@Override
public void run() {
for(int i=0;i<10;i++){
card.take(1000);
}
}
}
public class TestBankCard {
public static void main(String[] args) {
//创建银行卡
BankCard card = new BankCard();
//创建操作
AddMoney add = new AddMoney(card);
SubMoney sub = new SubMoney(card);
//创建线程对象
Thread t1 = new Thread(add,"男");
Thread t2 = new Thread(sub,"女");
t1.start();
t2.start();
}
}
#这时有2个线程对象,此时实现了存一次取一次
#多个线程存取时会出现问题,出现负数或2000,还有可能出现死锁
public class TestBankCard {
public static void main(String[] args) {
//创建银行卡
BankCard card = new BankCard();
//创建操作
AddMoney add = new AddMoney(card);
SubMoney sub = new SubMoney(card);
//创建线程对象
Thread t1 = new Thread(add,"男");
Thread t2 = new Thread(sub,"女");
Thread t3 = new Thread(add,"公");
Thread t4 = new Thread(sub,"母");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
#1.女抢到cpu,不能取,进入等待队列,释放锁和cpu
#2.母抢到cpu,不能取,进入等待队列,释放锁和cpu
#3.此时队列有2个
#4.男抢到cpu,存钱成功,修改标记true,并唤醒(随机唤醒队列中的一个)女
#5.女抢到cpu,取钱成功,修改标记为false,并唤醒母
#6.母抢到cpu,取钱成功,修改标记为false,并唤醒(此时队列没有) 为什么能取呢,因为刚才是走到if里面的wait()处进入队列的,此时唤醒没有再去判断标记
#此时将if换成while可以解决负数的问题,但是还有死锁问题
#死锁问题,根本原因随机唤醒一个,造成的问题
#女抢到cpu,不能取,进队列,释放锁和cpu 女
#母抢到cpu,不能取,进队列,释放锁和cpu 女 母
#男抢到cpu,存成功,修改标记唤醒女 母
#公抢到cpu,不能存,进队列,释放锁和cpu 母 公
#男抢到cpu,不能存,进队列,释放锁和cpu 母 公 男
#女抢到cpu,取成功,唤醒母 公 男
#女抢到cpu,不能取,进队列,释放锁和cpu 公 男 女
#母抢到cpu,不能取,进队列,释放锁和cpu 公 男 女 母
public class BankCard {
//余额
private double money;
//开关 标记
private boolean falg=false;//true表示有钱可以取钱,false没钱可以存钱
//存钱
public synchronized void save(double m){
while (falg){ //有钱
try {
this.wait();//进入等待队列,同时释放锁和cup
} catch (InterruptedException e) {
e.printStackTrace();
}
}
money=money+m;
System.out.println(Thread.currentThread().getName()+"存了"+m+"余额是"+money);
//修改标记
falg=true;
//唤醒取钱线程
this.notify();
}
//取钱
public synchronized void take(double m){
while (!falg){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
money=money-m;
System.out.println(Thread.currentThread().getName()+"取了"+m+"余额是"+money);
//修改标记
falg=false;
//唤醒存钱线程
this.notify();
}
}
#此时将notify()改为notifyAll()唤醒全部,即可解决问题
4.7 生产者和消费者
若干个生产者在生产产品,这些产品将提供给若干个消费者去消费,为了使生产者和消费者能并发执行,在两者之间设置一个能存储多个产品的缓冲区,生产者将生产的产品放入缓冲区中,消费者从缓冲区中取走产品进行消费,显然生产者和消费者之间必须保持同步,即不允许消费者到一个空的缓冲区中取产品,也不允许生产者向一个满的缓冲区中放入产品
package com.gx.d6;
/**
* 面包
*/
public class Bread {
private int id;
private String productName;
public Bread() {
}
public Bread(int id, String productName) {
this.id = id;
this.productName = productName;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
@Override
public String toString() {
return "Bread{" +
"id=" + id +
", productName='" + productName + '\'' +
'}';
}
}
package com.gx.d6;
/**
* 面包容器
*/
public class BreadCon {
//存放面包的数组
private Bread[] cons = new Bread[6];
//存放面包的位置
private int index = 0;
//存放面包
public synchronized void input(Bread b){//锁this
//判断有没有满
while (index>=cons.length){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
cons[index]=b;
System.out.println(Thread.currentThread().getName()+"生产了"+b.getId()+"");
index++;
//唤醒消费者
this.notifyAll();
}
//消费面包
public synchronized void output(){//锁this
while (index<=0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
index--;
Bread b = cons[index];
System.out.println(Thread.currentThread().getName()+"消费了"+b.getId());
cons[index]=null;
//唤醒生产者
this.notifyAll();
}
}
package com.gx.d6;
/**
* 生产
*/
public class Prodcut implements Runnable{
private BreadCon con;
public Prodcut(BreadCon con) {
this.con = con;
}
@Override
public void run() {
for (int i=0;i<30;i++){
con.input(new Bread(i,Thread.currentThread().getName()));
}
}
}
package com.gx.d6;
/**
* 消费
*/
public class Consume implements Runnable{
private BreadCon con;
public Consume(BreadCon con) {
this.con = con;
}
@Override
public void run() {
for (int i=0;i<30;i++){
con.output();
}
}
}
package com.gx.d6;
public class Test {
public static void main(String[] args) {
//容器
BreadCon con = new BreadCon();
//生产和消费
Prodcut prodcut = new Prodcut(con);
Consume consume = new Consume(con);
//创建线程对象
Thread chenchen = new Thread(prodcut,"晨晨生产者");
Thread bingbing = new Thread(consume,"冰冰消费者");
Thread mingming = new Thread(prodcut,"明明生产者");
Thread lili = new Thread(consume,"丽丽消费者");
//启动线程
chenchen.start();
bingbing.start();
}
}
4.8 总结
5. 线程池
5.1 线程池概念
5.2 线程池原理
将任务提交给线程池,由线程池分配线程,运行任务,并在当前任务结束后复用线程
5.3 创建线程池
常用的线程池接口和类(所在包java.util.concurrent)
- Executor:线程池的顶级接口 (源码查看作者)
- ExecutorService:线程池接口,可以通过submit(Runnable task)提交任务代码
- Executors工厂类:通过此类可以获得一个线程池
- 通过newFixedsThreadPool(int nThreads) 获取固定数量的线程池。参数:执行线程池中线程的数量
- 通过newCachedThreadPool() 获得动态数量的线程池,如果不够则创建新的,没有上线
package com.gx.d7;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 线程池的创建
* Executor:线程池的根接口 executor()
* Executor:包含管理线程池的一些方法 submit,shutdown等
* ThreadPoolExecutor
* ScheduledThreadPoolExecutor
* Executors:创建线程池的工具类
* (1)创建固定线程个数的线程池*
* (2)创建缓存线程池,由任务的多少决定*
* (3)创建单线程
* (4)创建调度线程池 调度:周期,定时执行
*/
public class Demo1 {
public static void main(String[] args) {
//1.1 创建固定个数的线程池
//ExecutorService es = Executors.newFixedThreadPool(4);
//1.2 创建缓存线程池
ExecutorService es = Executors.newCachedThreadPool();
//1.3 创建单线程池
//Executors.newSingleThreadExecutor();
//1.4 创建调度线程
//Executors.newScheduledThreadPool();
//2 创建任务
Runnable runnable = new Runnable() {
private int ticket=100;
@Override
public void run() {
while (true){
if (ticket<=0){
break;
}
System.out.println(
Thread.currentThread().getName()+"卖了第"+ticket+"张票"
);
ticket--;
}
}
};
//3. 提交任务
for (int i=0;i<4;i++){
es.submit(runnable);
}
//4. 关闭线程池
es.shutdown();//等待任务执行完毕
//es.shutdownNow()//立即关闭
}
}
5.4 Callable接口
JDK5加入,与Runnable接口类似,实现之后代表一个线程任务
Callable具有泛型返回值,可以声明异常
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* Callable接口的使用
* Callable和Runnable接口的区别
* (1)Callable接口中call方法有返回值,Runnable接口中run方法没有返回值
* (2)Callable接口中call方法有声明异常,Runnable接口中run方法没有异常
*/
public class Demo01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//功能需求:使用Callable实现1-100和
//1.创建Callable对象
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName()+"开始计算");
int sum = 0;
for (int i=0;i<100;i++){
sum+=i;
Thread.sleep(100);
}
return sum;
}
};
//2.把Callable对象转成可执行任务
FutureTask<Integer> task = new FutureTask<>(callable);
//3.创建线程
Thread thread = new Thread(task);
//4.启动线程
thread.start();
//5.获取结果(等待call执行完毕,才会返回)
Integer sum = task.get();
System.out.println("结果是:"+sum);
}
}
#结合线程池使用
import java.util.concurrent.*;
public class Demo02 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//1.创建线程池
ExecutorService es = Executors.newFixedThreadPool(1);
//2.提交任务
Future<Integer> future = es.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName()+"开始计算");
int sum =0;
for (int i=1;i<=100;i++){
sum+=i;
Thread.sleep(10);
}
return sum;
}
});
//3.获取任务结果
System.out.println(future.get());
//4.关闭线程
es.shutdown();
}
}
6. 线程安全的集合
待更新