线程
目录
线程执行方式
常用方法
实现多线程的4中方法:
1、继承Thread类,重写run方法
2、实现runnable接口,重写run方法
3、实现Callable接口,重写call()方法,call方法有返回值
4、通过线程池启动多线程
引用:https://blog.csdn.net/weixin_41891854/article/details/81265772
//实现runnable接口
public class RunableIml implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
}
//使用
public class TestRunnable {
public static void main(String[] args) {
RunableIml run = new RunableIml();
Thread t = new Thread(run);
t.start();
for (int i = 0; i < 200; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
}
实现runnable接口实现多线程的好处:
1、避免单继承的局限性,继承Thread类后就不能继承其他的类了,实现runnable接口还可以继承其他的类。
2、实现runnable接口增强了程序的扩展性,降低了程序的耦合性,实现runnable把设置线程任务与开启线程进行了分离。
重写的run方法来设置线程任务,创建线程Thread对象来开启新线程
new Thread时,传递不同的接口实现类完成不同的线程任务
class RunableIml1 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
}
class RunableIml2 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("hello "+i);
}
}
}
public class TestRunnable {
public static void main(String[] args) {
Thread t1 = new Thread(new RunableIml1());
t1.start();
Thread t2 = new Thread(new RunableIml2());
t2.start();
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
}
匿名内部类实现线程的创建:
好处:简化代码
把子类继承父类,重写父类方法,创建子类对象一步完成
把实现类实现接口,重写接口中的方法,创建实现类对象合成一步完成
匿名内部类的最终产物:子类/实现类对象,这个对象没名字
格式:new 父类/接口{
重写的方法
}
实现代码:
package testmap;
public class NiMingClass {
public static void main(String[] args) {
//使用Thread父类
new Thread(){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
}.start();
//使用runnable接口
Runnable r = new Runnable(){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("hello "+i);
}
}
};
Thread t = new Thread(r);
t.start();
//使用runnable接口更加简化
Thread t2 = new Thread(new Runnable(){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("word "+i);
}
}
});
t2.start();
}
}
多线程
并发与并行
进程和线程
进程:
Java进程通信的7中方式
线程:
线程调度
主线程:
单线程存在弊端:
多线程:
代码实现
package testmap;
/**
* 多线程:
* 实现步骤:
* 1、创建一个Thread的子类
* 2、在Thread的子类中重写Thread类中的run方法,设置线程任务
* 3、创建Thread的子类对象
* 4、调用Thread类中的start方法,开启新的线程,执行run方法
* 结果是两个程序并发的执行,当前线程和另一个线程。
* 多次启动一个线程是非法的。特别是当一个线程已经执行后,不能重新启动
*/
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("run "+i);
}
}
}
public class MultiThread {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.run();
for (int i = 0; i < 20; i++) {
System.out.println("main "+i);
}
}
}
线程安全问题
单线程不会出现线程安全问题
多线程不访问共享数据也不会有安全问题
只有多线程访问了共享数据才会出现安全问题
线程不安全的卖票案例:
package testmap;
/**
* 实现卖票案例
*
* 通过代码块中的锁对象,保证多个线程使用的锁是同一个
* 锁对象的作用:把同步代码块锁住,只让线程在同步代码块中执行
*/
class RunnableImpl implements Runnable{
private int tickets = 100;
//创建一个锁对象,锁可以是任意的
Object obj = new Object();
/**
* 卖票
*/
@Override
public void run() {
while(true){
// synchronized (obj) {
if (tickets > 0) {
try {
//提高线程不安全,卖票出错的可能性
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在卖票: " + tickets);
tickets--;
}
// }
}
}
}
/**
* 模拟卖票,创建三个线程,同时卖票
*/
public class Tickets {
public static void main(String[] args) {
RunnableImpl run = new RunnableImpl();
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
Thread t3 = new Thread(run);
t1.start();
t2.start();
t3.start();
}
}
改进后对的卖票
1、解决线程安全问题:同步代码块
/**
* 卖票
*/
@Override
public void run() {
while(true){
synchronized (obj) {
if (tickets > 0) {
try {
//提高线程不安全,卖票出错的可能性
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在卖票: " + tickets);
tickets--;
}
}
}
}
同步代码块原理:
2、解决线程安全问题:使用同步方法:
package testmap;
/**
* 实现卖票案例
*使用同步方法保证线程安全
* 把访问了共享数据的代码抽取出来,放到同步的方法里
* 使用同步关键字修饰方法
*/
class RunnableImpl2 implements Runnable{
private int tickets = 100;
public synchronized void payTicket(){
while(true){
if (tickets > 0) {
try {
//提高线程不安全,卖票出错的可能性
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在卖票: " + tickets);
tickets--;
}
}
}
/**
* 卖票
*/
@Override
public void run() {
payTicket();
}
}
/**
* 模拟卖票,创建三个线程,同时卖票
*/
public class Tickets2 {
public static void main(String[] args) {
RunnableImpl2 run = new RunnableImpl2();
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
Thread t3 = new Thread(run);
t1.start();
t2.start();
t3.start();
}
}
3、解决线程安全问题:使用Lock锁
package testmap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 实现卖票案例
* 使用Lock锁来保证线程安全
*/
class RunnableImpl3 implements Runnable{
private int tickets = 100;
Lock lock = new ReentrantLock();
/**
* 卖票
*/
@Override
public void run() {
while(true){
lock.lock();
if (tickets > 0) {
try {
//提高线程不安全,卖票出错的可能性
Thread.sleep(10);
System.out.println(Thread.currentThread().getName() + "正在卖票: " + tickets);
tickets--;
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
}
}
/**
* 模拟卖票,创建三个线程,同时卖票
*/
public class Tickets3 {
public static void main(String[] args) {
RunnableImpl3 run = new RunnableImpl3();
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
Thread t3 = new Thread(run);
t1.start();
t2.start();
t3.start();
}
}
线程间的通信
java实现线程间通信的四种方式
1、synchronized同步
2、while轮询
3、wait/notify机制
4、管道通信
等待唤醒机制
waiting状态不会浪费CPU也不会竞争锁,等待的线程需要notify来唤醒它
案例:生产者消费者(吃包子案例)
代码:包子类
package baozi;
/**
* 包子类
*/
public class Baozi {
public String pi;
public String xian;
boolean flag;
}
包子铺
package baozi;
/**
*包子铺
*/
public class BaoZiPu extends Thread {
private Baozi baozi;
public BaoZiPu(Baozi baozi) {
this.baozi = baozi;
}
@Override
public void run() {
int count = 0;
while(true){
synchronized (baozi){
if(baozi.flag){
//有包子就等待
try {
baozi.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//被唤醒后开始生产包子
if(count%2==0){
baozi.pi = "薄皮";
baozi.xian = "三鲜馅";
}else{
baozi.pi = "冰皮";
baozi.xian = "牛肉馅";
}
count++;
System.out.println("包子铺正在生产"+baozi.pi+baozi.xian+"的包子");
//3秒生产包子
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//修改包子的状态为有
baozi.flag = true;
//唤醒吃货进程
baozi.notify();
//
System.out.println("包子铺已经生产好了"+baozi.pi+baozi.xian+"的包子吃货可以吃了");
}
}
}
}
吃货类
package baozi;
/**
* 吃货
*/
public class ChiHuo extends Thread{
private Baozi baozi;
public ChiHuo(Baozi baozi){
this.baozi = baozi;
}
@Override
public void run() {
//while死循环,让吃货一直吃包子、
while(true){
synchronized (baozi){
if(!baozi.flag){
//没包子,吃货等待
try{
baozi.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//被唤醒之后,开始吃包子
System.out.println("吃货正在吃"+baozi.pi+baozi.xian+"包子");
baozi.flag = false;
//唤醒包子铺线程
baozi.notify();
System.out.println("吃货吃完了"+baozi.pi+baozi.xian+"包子,包子铺开始生产");
}
}
}
}
测试类
package baozi;
public class TestBaozi {
public static void main(String[] args) {
Baozi baozi = new Baozi();
new BaoZiPu(baozi).start();
new ChiHuo(baozi).start();
}
}
线程的状态:6种
线程的状态转换图
1、新建
2、可运行
3、结束
4、无限等待
锁.wait(),不带时间参数
5、计时等待:
锁.sleep()计时等待期间,与锁无关,锁资源不被释放,等待时间后只需要抢夺cpu。
锁.wait(时间参数),带时间参数,会释放锁资源,案例如下
package notify_wait;
public class WaitDaiCanDemo2 {
public static void main(String[] args) {
Object obj = new Object();
new Thread(){
@Override
public void run() {
synchronized (obj){
try {
System.out.println("线程1 开始 wait(1000)");
obj.wait(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
new Thread(){
@Override
public void run() {
synchronized (obj) {
System.out.println("线程2开始执行");
System.out.println(obj);
}
}
}.start();
}
}
6、计时等待有两种方式
1、sleep(毫秒值):毫秒值结束就会自动醒来。
2、wait(毫秒值):如果,wait在毫秒值结束之前一直没被唤醒,那么他就会自动醒来
案例代码:
package notify_wait;
public class WaitDaiCan {
public static void main(String[] args) {
//创建锁对象
Object obj = new Object();
//创建一个顾客线程(消费者)
new Thread(){
@Override
public void run() {
while (true){
synchronized (obj){
System.out.println("顾客告知老板要的包子种类和数量");
//调用wait进入无限等待状态
try {
obj.wait(1000);
//线程被唤醒后,会继续执行wait之后的代码
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}.start();
}
}
6、锁阻塞:
Blocked状态:一个正在阻塞等待锁对象的线程处于这一状态。
唤醒wait()线程的两种方式:
1、notify():随机唤醒一个正在等待这个锁的线程
2、notifyAll():唤醒所有正在等待这个锁的线程。
还是吃包子的案例:
package notify_wait;
public class WaitAndNotify {
public static void main(String[] args) {
//创建锁对象
Object obj = new Object();
//创建一个顾客线程(消费者)
new Thread(){
@Override
public void run() {
while (true){
synchronized (obj){
System.out.println("顾客告知老板要的包子种类和数量");
//调用wait进入无限等待状态
try {
obj.wait();
//线程被唤醒后,会继续执行wait之后的代码
} catch (InterruptedException e) {
e.printStackTrace();
}
//线程被唤醒后,会继续执行wait之后的代码
System.out.println("顾客开始吃包子");
}
}
}
}.start();
//创建一个老板线程(生产者)
new Thread(){
@Override
public void run() {
while(true){
System.out.println("老板开始做包子,需要5秒");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj){
//老板花5s做包子
obj.notify();
System.out.println("老板做好了包子,告知顾客");
}
}
}
}.start();
}
}
线程池:
概念
合理利用线程池的三个好处:
/**
* 线程池的使用步骤
* 1、使用线程池的工厂类Executor里面提供的newFixedThreadPool生产一个指定线程数量的线程池
* 2、创建一个雷,实现Runnable接口,重写run方法
* 3、调用ExecutorService中的submit方法,传递线程(实现类),开启线程,执行run方法
* 4、调用ExecutorService中的shutdown方法,销毁线程池(不建议,线程池就是为了重复使用)
*/
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 创建线程实现类,实现Runnable接口,重写run方法,设置线程任务
*/
class RunnableImplement implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"创建了一个新的线程");
}
}
public class ThreadPoll {
public static void main(String[] args) {
//使用线程工厂类Ex而粗铜仁市里边提供的静态方法,创建指定线程个数的线程池
ExecutorService executorService = Executors.newFixedThreadPool(4);
executorService.submit(new RunnableImplement());
executorService.submit(new RunnableImplement());
executorService.submit(new RunnableImplement());
executorService.submit(new RunnableImplement());
executorService.submit(new RunnableImplement());
executorService.submit(new RunnableImplement());
executorService.submit(new RunnableImplement());
executorService.submit(new RunnableImplement());
executorService.shutdown();
}
}
线程池用完会归还,会使用多次