理解多线程(java.Thread)
线程简介
进程:
应用程序的执行实例,有独立内存空间和系统资源
线程:
cpu调度和分派的基本单位,进程中执行运算最小的单位,可完成一个独立顺序控制流程
多线程:
在一个进程中同时运行了多个线程,完成不同的工作
多个线程交替占用cpu资源,而非真正并行执行
好处:
充分利用cpu资源
简化编程模型
带来良好用户体验
创建线程的三种方式
- 继承Thread class
- 实现Runnable 接口(重要)
- 实现Callable 接口
继承Thread类
- 一个类继承Thread类。
- 从写run()方法,写入需要执行的方法。
- 实例化类,通过实例调用start()方法开启线程。
public class CustomThread extends Thread {
//重写的run方法
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("run"+i);
}
}
public static void main(String[] args) {
CustomThread customThread = new CustomThread();
customThread.start();
for (int i = 0; i < 20; i++) {
System.out.println("main"+i);
}
}
}
这里实例是不能调用run()方法。
不过这个方式在jdk1.5之后就已经过时了。
实现Runnable接口
- 类实现Runnable接口
- 重写Run()方法
- 实例化rRunnable接口,然后new Thread()调用start()方法将实例化的接口放入Thread中。
public class RunnableTest implements Runnable {
//重写的run方法
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("run"+i);
}
}
public static void main(String[] args) {
RunnableTest runnableTest = new RunnableTest();
new Thread(runnableTest).start();
for (int i = 0; i < 20; i++) {
System.out.println("main"+i);
}
}
}
实现这个接口在于这样的方式可以开启多个线程操控同一个实例。简单实例看代码:
public class RunnableTest2 implements Runnable {
private int ticketNum = 0;
public void run() {
while (true){
if(ticketNum<=0){
break;
}
//睡眠,模拟延时操作
try {
Thread.sleep(200L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"===>拿到了第"+ticketNum+"张票");
}
}
public static void main(String[] args) {
RunnableTest2 runnableTest2 = new RunnableTest2();
//开启多条线程
new Thread(runnableTest2,"小何").start();
new Thread(runnableTest2,"小心").start();
new Thread(runnableTest2,"小牛").start();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JRkgBlxX-1611478756729)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1611386506491.png)]
但是发现这样开启多线程有一个问题(不安全),多个线程就会抢夺同一个资源,这也是一个简单的并发问题。
下面来一个经典龟兔赛跑小实例:
public class Race implements Runnable {
//胜利者的名字
private String winner;
public void run() {
for (int i = 0; i <= 100; i++) {
//这里模拟“博尔特儿”跑的太快了,在终点前休息等待刘翔儿
if(Thread.currentThread().getName().equals("博尔特儿")&&i==98){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(isOver(i)){
break;
}
System.out.println(Thread.currentThread().getName()+"=====>跑了"+i+"步");
}
}
//判断比赛是否已经结束
private boolean isOver(int i){
if(winner != null){
return true;
}{
if(i >= 100){
winner = Thread.currentThread().getName();
System.out.println("胜利者是"+winner);
return true;
}
}
return false;
}
public static void main(String[] args) {
Runnable runnable = new Race();
new Thread(runnable,"博尔特儿").start();
new Thread(runnable,"刘翔儿").start();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gIZ56egO-1611478756733)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1611388073353.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zpCIwDi1-1611478756736)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1611388081906.png)]
实现Callable接口
简单案例:
//这里<>中写道是call方法中返回值类型
public class CallableTest implements Callable<Boolean> {
private String name;
private int ticketNum = 1;
public CallableTest(String name) {
this.name = name;
}
public Boolean call() throws Exception {
while (true){
if(ticketNum<=0){
break;
}
//睡眠,模拟延时操作
try {
Thread.sleep(200L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+"===>拿到了第"+ticketNum--+"张票");
}
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
CallableTest runnableTest2 = new CallableTest("小何");
CallableTest runnableTest3 = new CallableTest("小牛");
//创建执行服务
ExecutorService service = Executors.newFixedThreadPool(3);
//提交执行
Future<Boolean> r1 = service.submit(runnableTest2);
Future<Boolean> r2 = service.submit(runnableTest3);
//获取结果
boolean rs1 = r1.get();
boolean rs2 = r2.get();
//关闭执行服务
service.shutdownNow();
}
}
线程五大状态
-
创建状态
也就是new
-
就绪状态
当调用了start()方法是,线程就进入了就绪状态
-
阻塞状态
当调用了sleep或wait方法时
-
运行状态
由就绪状态可进入运行状态
-
死亡状态
线程结束
线程方法
暂停
不推荐使用jdk中推出的stop方法和destroy方法,可以看到jdk中这方法也被遗弃了(destroy)
推荐的暂停方法就是通过标记(flag)来暂停。
public class ThreadFunctionTest implements Runnable{
private boolean flag = true;
@Override
public void run() {
while (flag){
int i = 0;
System.out.println("Thread ..... run " + i++);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//改变flag值可以将现场结束
public void stop(){
this.flag = false;
}
public static void main(String[] args) throws InterruptedException {
ThreadFunctionTest threadFunctionTest = new ThreadFunctionTest();
new Thread(threadFunctionTest).start();
for (int i = 0; i < 50; i++) {
System.out.println("main ... run " + i);
Thread.sleep(50);
if(i == 25){
threadFunctionTest.stop();
}
}
}
}
睡眠
Thread.sleep(Long time);
//线程会进入阻塞状态
礼让
礼让其实就是重新调配cpu,使线程重新竞争资源。并不一定可以成功。
public class TestYield implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"开始======>");
//礼让
Thread.yield();
System.out.println(Thread.currentThread().getName()+"结束======>");
}
public static void main(String[] args) {
TestYield testYield = new TestYield();
new Thread(testYield,"a").start();
new Thread(testYield,"b").start();
}
}
join,强制执行
强制执行线程,使其他线程进入阻塞状态。程序中尽量少用容易造成程序阻塞。(可以理解为插队)
public class TestJoin implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"=======>"+i);
}
}
public static void main(String[] args) throws InterruptedException {
TestJoin testJoin = new TestJoin();
Thread thread = new Thread(testJoin,"a");
thread.start();
for (int i = 0; i < 50; i++) {
System.out.println("main ... run"+i);
if(i == 25){
//强制执行
thread.join();
}
}
}
}
Lambda表达式
lambda表达式是为了简化代码用的。
简单认识一下lambda,创建一个线程:
new Thread(()->System.out.print("多线程学习")).start();
lambda表达式是在java 8后引入的,在这之前需要理解一下函数式接口Function Interface
Function Interface:
- 任何接口,如果只包含唯一一个抽象方法,那么他就是一个函数式接口
- 对于函数式接口,我们可以通过lambda表达式来创建该接口的对象。
public class lambdaTest {
public static void main(String[] args) {
//使用lambda首先接口要是函数型接口
Inter inter = (int a,String b) -> {
System.out.println("hello world 1"+a+b);
};
//省略参数类型一个省略全需省略,若参数只有一个可以省略(),
inter = (a,b) -> {
System.out.println("hello world 1"+a+b);
};
//若执行代码只有一行可以省略{}
inter = (a,b) -> System.out.println("hello world 1"+a+b);
inter.hello(1,"2");
}
}
interface Inter{
void hello(int a,String b);
}
class InterImpl implements Inter{
public void hello(int a,String b) {
System.out.println("hello world 1"+a+b);
}
}
Thread静态代理
静态代理模式我的请看设计模式
Thread类中,我们可以在源码或者java的文档中看到Thread是实现了Runnable接口的,这里其实就可以把Runnable看成一个实例,二Thread就是一个代理(Proxy)。不理解的可以去看一下我的设计模式。
观测线程状态
Thread.State
public class ThreadState {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("****************");
});
//新生状态
Thread.State state = thread.getState();
System.out.println(state);
thread.start();
//就绪状态
state = thread.getState();
System.out.println(state);
//监测线程状态
while (state != Thread.State.TERMINATED){
state = thread.getState();
System.out.println(state);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程优先级
priority
先设置优先级后开启线程。
public class PriorityTest {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+"**********"+Thread.currentThread().getPriority());
Runnable r1 = ()-> System.out.println(Thread.currentThread().getName()+"**********"+Thread.currentThread().getPriority());
Thread t1 = new Thread(r1,"r1");
Thread t2 = new Thread(r1,"r2");
Thread t3 = new Thread(r1,"r3");
Thread t4 = new Thread(r1,"r4");
//设置最小优先级为1
t1.setPriority(Thread.MIN_PRIORITY);
t1.start();
//设置默认优先级为5
t2.setPriority(Thread.NORM_PRIORITY);
t2.start();
//设置最自定义优先级
t3.setPriority(6);
t3.start();
//设置最大优先级为10
t4.setPriority(Thread.MAX_PRIORITY);
t4.start();
}
}
但是优先级也不一定生效,也是由cpu调度的
守护线程(daemon)
public class TestDaemon {
public static void main(String[] args) {
Runnable r1 = ()->{
while (true){
System.out.println("我是守护进程!");
}
};
Runnable r2 = ()->{
for (int i = 0; i < 365; i++) {
System.out.println("我活了"+i+"天了!");
}
System.out.println("我结束了!");
};
Thread thread = new Thread(r1);
//设置r1位守护进程!
thread.setDaemon(true);
thread.start();
new Thread(r2).start();
}
}
线程同步
队列和锁。synchronized锁。安全但是存在性能问题。
**同步块:**synchronized(Obj){}
1.synchronized属性,在之前讲的那个买车票的问题中,出现的一个线程不安全的问题,就比如车票等出现负一等。只要在改变车票数的方法上加一个synchronized属性,就比如让他们排队买票。
public class TestSyn1 implements Runnable{
private int ticketNum = 10;
@Override
public void run() {
while (true){
if(ticketNum<=0){
break;
}
//睡眠,模拟延时操作
try {
Thread.sleep(200L);
} catch (InterruptedException e) {
e.printStackTrace();
}
check();
}
}
//这里增加了synchronized
private synchronized void check(){
if(ticketNum<=0){
return;
}
System.out.println(Thread.currentThread().getName()+"===>拿到了第"+ticketNum--+"张票");
}
public static void main(String[] args) {
TestSyn1 testSyn1 = new TestSyn1();
//开启多条线程
new Thread(testSyn1,"小何").start();
new Thread(testSyn1,"小心").start();
new Thread(testSyn1,"小牛").start();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5NdCGBWo-1611478756742)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1611470955799.png)]
另一个例子,需要用到同步块,传入同步监视器,这个同步监视器最好是一个线程执行中改变的量。
public class TestSyn2 {
public static void main(String[] args) {
Account account = new Account(100);
Staction st1 = new Staction(account,50);
Staction st2 = new Staction(account,100);
new Thread(st1,"我").start();
new Thread(st2,"你").start();
}
}
//假设这是你的银行账户
class Account{
//账户余额
public int allMoney;
public Account(int allMoney) {
this.allMoney = allMoney;
}
}
//假设这是银行
class Staction implements Runnable{
//你的账户
private Account account;
//你要取的钱
private int money;
public Staction(Account account,int money){
this.account = account;
this.money = money;
}
@Override
public void run() {
//设置同步块变量为account
synchronized (account) {
if (account.allMoney - money < 0) {
System.out.println(Thread.currentThread().getName()+"钱不够了,无法取钱啦");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.allMoney = account.allMoney - money;
System.out.println(Thread.currentThread().getName() + "取了" + money + "万元,账户中还剩余" + account.allMoney);
}
}
}
我们知道ArrayList()是线程不安全的集合,可能因为两个线程同时操作一个ArrayList将值覆盖了。但是只要加上同步块就可以解决。
public class TestSyn3 {
public static void main(String[] args) {
List<String> list = new ArrayList();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
synchronized (list) {
list.add(Thread.currentThread().getName());
}
}).start();
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
JUC CopyOnWriteArrayList线程安全的集合
public class TestJUC {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
死锁
产生死锁的必要条件
- 互斥条件:一个资源一次只能被一个进程使用。
- 请求与保持条件:一个进程因请求资源而进入阻塞状态,对已获得资源保持不放。
- 不剥夺条件:进程已获得资源,在未使用完前,不得强行剥夺。
- 循环等待条件:若干进程收尾相连形成一种循环等待资源关系。
简单例子:
public class TestDead {
public static void main(String[] args) {
ThreadTest t1 = new ThreadTest("小光",0);
ThreadTest t2 = new ThreadTest("小亮",1);
t1.start();
t2.start();
}
}
class Test1{}
class Test2{}
class ThreadTest extends Thread{
Test1 test1 = new Test1();
Test2 test2 = new Test2();
String name;
int choose ;
ThreadTest(String name,int choose){
this.name = name;
this.choose = choose;
}
@Override
public void run() {
if(choose == 0){
synchronized (test1){
System.out.println(name+"拿到了test1,正在等待test2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (test2){
System.out.println(name+"拿到了test2,正在等待test1");
}
}
}else{
synchronized (test2){
System.out.println(name+"拿到了test2,正在等待test1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (test1){
System.out.println(name+"拿到了test1,正在等待test2");
}
}
}
}
}
Lock锁
可重入锁
public class TestLock implements Runnable{
private int ticketNum = 10;
//lock锁
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
try {
lock.lock();
//睡眠,模拟延时操作
try {
Thread.sleep(200L);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(ticketNum>0) {
System.out.println(Thread.currentThread().getName() + "===>拿到了第" + ticketNum-- + "张票");
}else {
break;
}
}finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
TestLock testLock = new TestLock();
//开启多条线程
new Thread(testLock,"小何").start();
new Thread(testLock,"小心").start();
new Thread(testLock,"小牛").start();
}
}
线程协作
生产者消费者模式
解决方法1(缓冲区,管程法):
public class TestOne {
public static void main(String[] args) {
Buffer buffer = new Buffer();
new Producer(buffer).start();
new Consumer(buffer).start();
}
}
//汉堡
class Hamburger{
public int id;
public Hamburger(int id){
this.id = id;
}
}
//生产者
class Producer extends Thread{
private Buffer buffer;
public Producer(Buffer buffer){
this.buffer = buffer;
}
@Override
public void run() {
try {
for (int i = 0; i < 100; i++) {
buffer.Push(new Hamburger(i));
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//消费者
class Consumer extends Thread{
private Buffer buffer;
public Consumer(Buffer buffer){
this.buffer = buffer;
}
@Override
public void run() {
try {
for (int i = 0; i < 100; i++) {
buffer.Pop();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//缓冲区
class Buffer{
//缓冲区中有汉堡的数量
int count = 0;
//缓冲区中最大汉堡的数量
int Max_Count = 10;
//缓冲区中汉堡的数组
Hamburger[] hamburgers = new Hamburger[Max_Count];
//生产者把汉堡放入缓冲区
public synchronized void Push(Hamburger hamburger) throws InterruptedException {
//如果缓冲区中的汉堡放满了,则生产者停止生产
if(count == hamburgers.length){
//生产者等待,通知消费者消费
this.wait();
}
//生产者把汉堡放到缓冲区
hamburgers[count] = hamburger;
System.out.println("生产者放入了第"+hamburger.id+"个汉堡");
count++;
//通知消费者
this.notifyAll();
}
//消费者消费缓冲区中的产品
public synchronized void Pop() throws InterruptedException {
//若缓冲区中没有产品,者消费者等待通知生产者
if(count == 0){
//消费者等待
this.wait();
}
//消费者消费缓冲区中的产品
System.out.println("消费者消费了第"+hamburgers[count-1].id+"的汉堡");
count--;
//通知生产者
this.notifyAll();
}
}
解决方法2:(信号灯法)
public class TestTwo {
public static void main(String[] args) {
Flag flag = new Flag();
new Producer1(flag).start();
new Consumer1(flag).start();
}
}
//生产者
class Producer1 extends Thread{
private Flag flag;
public Producer1(Flag flag){
this.flag = flag;
}
@Override
public void run() {
try {
for (int i = 0; i < 20; i++) {
flag.Push(i);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//消费者
class Consumer1 extends Thread{
private Flag flag;
public Consumer1(Flag flag){
this.flag = flag;
}
@Override
public void run() {
try {
for (int i = 0; i < 20; i++) {
flag.Pop();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//信号标志类
class Flag{
int msg;
//默认flag为true
boolean flag = true;
//生产者生产
public synchronized void Push(int msg) throws InterruptedException {
//如果flag标志位不为true生产者就等待
if(!flag){
this.wait();
}
this.msg = msg;
System.out.println("生产者生产了:=======>"+this.msg);
//通知消费者唤醒他们
this.notifyAll();
//改变标志位
this.flag = !this.flag;
}
//消费者消费
public synchronized void Pop() throws InterruptedException {
//如果flag标志位为true消费者就等待
if(flag){
this.wait();
}
System.out.println("消费者消费了:=======>"+this.msg);
//通知生产者生产唤醒
this.notifyAll();
//改变标志位
this.flag = !this.flag;
}
}
线程池
线程经常的创建和销毁,在线程很多的情况下特别是并发情况,这样的方式非常的耗费资源。所以就需要线程池,将线程先创建好,放入一个池中,用完可以放回池中,避免频繁的销毁创建、实现重复利用。总之好处多多
//这里<>中写道是call方法中返回值类型
public class CallableTest implements Callable<Boolean> {
private String name;
private int ticketNum = 1;
public CallableTest(String name) {
this.name = name;
}
public Boolean call() throws Exception {
while (true){
if(ticketNum<=0){
break;
}
//睡眠,模拟延时操作
try {
Thread.sleep(200L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+"===>拿到了第"+ticketNum--+"张票");
}
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
CallableTest runnableTest2 = new CallableTest("小何");
CallableTest runnableTest3 = new CallableTest("小牛");
//创建执行服务
ExecutorService service = Executors.newFixedThreadPool(3);
//提交执行 service.execute() 是Runnable的线程池调用方法,没有返回值
Future<Boolean> r1 = service.submit(runnableTest2);
Future<Boolean> r2 = service.submit(runnableTest3);
//获取结果
boolean rs1 = r1.get();
boolean rs2 = r2.get();
//关闭执行服务
service.shutdownNow();
}
}