Java中线程实现的方式
在 Java 中实现多线程有两种手段,一种是继承 Thread 类,另一种就是实现 Runnable 接口。下面我们就分别来介绍这两种方式的使用。
方式一:实现runnable接口
实现原理顺序:
1:定义ThreadTest02 类,实现Runnable接口
2:实现run()方法,编写线程执行体
3:创建线程对象,调用start()方法启动线程
public class ThreadTest02 implements Runnable {
private String name; // 表示线程的名称
public static void main(String[] args) {
ThreadTest02 threadTest02=new ThreadTest02("A任务");
ThreadTest02 threadTest021=new ThreadTest02("B任务");
Thread thread=new Thread(threadTest02);
Thread thread1=new Thread(threadTest021);
thread.start();
thread1.start();
}
public ThreadTest02(String name) {
this.name = name; // 通过构造方法配置name属性
}
public void run() { // 覆写run()方法,作为线程 的操作主体
for (int i = 0; i < 10; i++) {
System.out.println(name + "运行,i = " + i);
}
}
}
执行的结果如下:
"C:\Program Files\Java\jdk1.8.0_221\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2020.2.3\lib\idea_rt.jar=59905:C:\Program Files\JetBrains\IntelliJ IDEA 2020.2.3\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_221\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\rt.jar;F:\工作项目\JAVA开发工具\JAVA学习代码\基础语法\out\production\Demo01" com.ding.ThreadPack.ThreadTest02
A任务运行,i = 0
B任务运行,i = 0
B任务运行,i = 1
B任务运行,i = 2
B任务运行,i = 3
B任务运行,i = 4
B任务运行,i = 5
B任务运行,i = 6
B任务运行,i = 7
B任务运行,i = 8
B任务运行,i = 9
A任务运行,i = 1
A任务运行,i = 2
A任务运行,i = 3
A任务运行,i = 4
A任务运行,i = 5
A任务运行,i = 6
A任务运行,i = 7
A任务运行,i = 8
A任务运行,i = 9
Process finished with exit code 0
方式二:继承 Thread 类
public class ThreadTest04 extends Thread {
private String url;
private String name;
public ThreadTest04(String url, String name) {
this.url = url;
this.name = name;
}
public static void main(String[] args) {
String url="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1606022249183&di=cda159950b8d15998b1c485d59c9ed41&imgtype=0&src=http%3A%2F%2Fimg.kutoo8.com%2Fupload%2Fimage%2F73370865%2F140-150313102339%2520%25281%2529_960x540.jpg";
ThreadTest04 threadTest01=new ThreadTest04(url,"1.jpg");
ThreadTest04 threadTest02=new ThreadTest04(url,"2.jpg");
ThreadTest04 threadTest03=new ThreadTest04(url,"3.jpg");
//先下载t1
threadTest01.start();
//然后t2
threadTest02.start();
//再下载3
threadTest03.start();
//但实际效果却不一定相同顺序
}
@Override
public void run() {
WebDownLoader webDownLoader = new WebDownLoader();
webDownLoader.downLoader(url, name);
System.out.println("下载了图片为:"+name);
}
}
class WebDownLoader {
public void downLoader(String url, String name) {
try {
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (IOException e) {
System.out.println("WebDownLoader方法异常" + e.getMessage());
}
}
}
效果也是一样的
总结:
继承Thread方式:
1:子类继承Thread类,具备多线程能力,
2:启动线程 子类对象.start();
3:不建议使用:避免OOP单继承的局限性
实现Runnable接口方式:
1:实现Runnable接口具有多线程能力
2:启动线程:传入目标对象+Thread对象+start()
3:推荐使用,避免单继承局限性,灵活方便,方便同一个对象被多个线程访问
方式三:实现callable接口 重写call方法 实现多现程模式(用得比较少)
实现代码如下:
/**
* 创建线程方式三 实现callable接口
* callable的好处:
* 1:可以定义返回值
* 2:可以抛出异常
*/
public class CallableTest01 implements Callable {
private String url;
private String name;
public CallableTest01(String url, String name) {
this.url = url;
this.name = name;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
String url="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1606022249183&di=cda159950b8d15998b1c485d59c9ed41&imgtype=0&src=http%3A%2F%2Fimg.kutoo8.com%2Fupload%2Fimage%2F73370865%2F140-150313102339%2520%25281%2529_960x540.jpg";
CallableTest01 threadTest01=new CallableTest01(url,"1.jpg");
CallableTest01 threadTest02=new CallableTest01(url,"2.jpg");
CallableTest01 threadTest03=new CallableTest01(url,"3.jpg");
//创建执行服务
ExecutorService service= Executors.newFixedThreadPool(3);
//提交执行
Future<Boolean> r1=service.submit(threadTest01);
Future<Boolean> r2=service.submit(threadTest02);
Future<Boolean> r3=service.submit(threadTest03);
//获取结果
boolean res1=r1.get();
boolean res2=r2.get();
boolean res3=r3.get();
System.out.println(res1);
System.out.println(res2);
System.out.println(res3);
service.shutdown();
}
@Override
public Boolean call() throws Exception {
WebDownLoader webDownLoader = new WebDownLoader();
webDownLoader.downLoader(url, name);
System.out.println("下载了图片为:"+name);
return true;
}
}
二:初识并发问题
说明:多线程操作同一资源默认情况下会产生数据混乱,单个资源被多个线程消费,直接上代码如下
//多个线程同时操作同一个对象
//买火车票案例
//发现问题:多个线程操作同一资源下,线程不安全,数据混乱
public class ThreadTest06 implements Runnable{
//票数
private int ticketNums=10;
@Override
public void run() {
while (true) {
if(ticketNums<=0){
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-拿到了第"+ticketNums--+"张票");
}
}
public static void main(String[] args) {
ThreadTest06 threadTest06=new ThreadTest06();
new Thread(threadTest06,"小明").start();
new Thread(threadTest06,"老师").start();
new Thread(threadTest06,"黄牛党").start();
}
}
运行结果如下图:
"C:\Program Files\Java\jdk1.8.0_221\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2020.2.3\lib\idea_rt.jar=51385:C:\Program Files\JetBrains\IntelliJ IDEA 2020.2.3\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_221\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_221\jre\lib\rt.jar;F:\工作项目\JAVA开发工具\JAVA学习代码\基础语法\out\production\Demo01;F:\工作项目\JAVA开发工具\JAVA学习代码\工具包\commons-io-2.8.0\commons-io-2.8.0.jar" com.ding.ThreadPack.ThreadTest06
老师-拿到了第10张票
黄牛党-拿到了第7张票
小明-拿到了第8张票
俗某某-拿到了第9张票
丁某某-拿到了第6张票
黄牛党-拿到了第4张票
小明-拿到了第3张票
丁某某-拿到了第2张票
老师-拿到了第5张票
俗某某-拿到了第1张票
小明-拿到了第0张票
黄牛党-拿到了第-1张票
Process finished with exit code 0
三:静态代理初识
静态代理模式总结:
真实对象和代理对象都要实现同一接口
代理对象要代理真实角色
四:lambda表达式(JDK8新增功能)
public class LambdaTest01 {
/*
//方式2 静态内部类
static class Love implements Ilove {
@Override
public void love(int a) {
System.out.println("i love you->" + a);
}
}*/
public static void main(String[] args) {
/*
//方式三:局部内部类
class Love implements Ilove {
@Override
public void love(int a) {
System.out.println("i love you->" + a);
}
}*/
//Ilove ilove=new Love();
//方式四:匿名内部类 (经简化成为下面这样)
/* Ilove ilove=new Ilove() {
@Override
public void love(int a) {
System.out.println("i love you->" + a);
}
};*/
//简化化为lambda表达式--简化方法
/* Ilove ilove=(int a)-> {
System.out.println("i love you->" + a);
};*/
//再简化--简化类型
/*Ilove ilove=(a)->{
System.out.println("i love you->" + a);
System.out.println("i love you->too" + a);
};
*/
//再简化--去掉花括号
Ilove ilove=(a,b)-> System.out.println("i love you->" + a+"---"+b);
//总结:
/**
* 1:lambda表达式只有一行代码下才能简化成为一行,如果有多行 就不能花括号
* 2:前提接口为函数接口 (接口里面只有一个方法 ,多个方法就不行)
* 3:多个参数也可以去掉类型,要去掉都去掉,还必须加括号(Ilove ilove=(a,b)-> System.out.println("i love you->" + a+"---"+b);)
*
*/
ilove.love(520,521);
}
}
interface Ilove{
void love(int a ,int b);
}
//方式1:外部类
/*class Love implements Ilove {
@Override
public void love(int a) {
System.out.println("i love you->" + a);
}
}*/
lambda表达式总结:
* 1:lambda表达式只有一行代码下才能简化成为一行,如果有多行 就不能花括号
* 2:前提接口为函数接口 (接口里面只有一个方法 ,多个方法就不行)
* 3:多个参数也可以去掉类型,要去掉都去掉,还必须加括号(Ilove ilove=(a,b)-> System.out.println("i love you->" + a+"---"+b);)
五:线程状态(五个状态:新生状态(NEW),就绪状态(RUNNABLE),运行状态(TIMED_WAITING),阻塞状态(TIMED_WAITING,WAITING),死亡状态(TERMINATED))
六:线程的优先级
说明:
1:java提供一个线程调度器来监控程序中启动后进行就绪状态的所有进程,线程调度器按照优先级别决定应该调用哪个线程来执行。
2:线程优先级用数字表示,范围为:1-10.
/**
* The minimum priority that a thread can have.
*/
public final static int MIN_PRIORITY = 1;
/**
* The default priority that is assigned to a thread.
*/
public final static int NORM_PRIORITY = 5;
/**
* The maximum priority that a thread can have.
*/
public final static int MAX_PRIORITY = 10;
3:使用下列的方式可获得或者设置线程优先级
thread.getPriority()
thread.setPriority(1);
七:守护线程
1:线程分为用户线程和守护线程
2:虚拟机必须确保用户线程执行完毕 (main线程)
3:虚拟机不用执行守护线程执行完毕(gc回收)
4:如后台记录操作日志,监控日志,垃圾回收等等
//测试守护线程
//上帝保护你
public class DaemonThreadTest01 {
public static void main(String[] args) {
God god=new God();
You you=new You();
Thread thread=new Thread(god);
thread.setDaemon(true);//默认是false 表示用户线程,正常的线程都是用户线程
thread.start();//上帝守护线程启动
new Thread(you).start();//用户线程启动
}
}
//上帝
class God implements Runnable{
@Override
public void run() {
while (true){
System.out.println("上帝永远保佑着你");
}
}
}
//你
class You implements Runnable{
@Override
public void run() {
for (int i = 0; i < 3650; i++) {
System.out.println("你一生都开心的活着!");
}
System.out.println("goodbye world ");
}
}
八:线程同步(形成条件:线程+锁)--只正对于增删改查的对象
说明:现实 生活中,我们会遇到“同一个资源,多个人都想使用”的问题,比如食堂打饭,每个人都想吃饭,最天然的解决办法就是排队,一个一个来。
注:处理多线程问题时,多个线程访问同一对象,并且某些线程还想修改这个对象,这时我们就需要线程同步,线程同步其实就是一种等待机制,多个需要同时访问此对象的纯种进入这个“对象的等待池”形成队列,等待前面的线程使用完毕,下一个线程再使用。
这里先做个不安全的案例:取钱案例
//不安全的取钱
//两个人去银行取钱,账户
public class UnSafeBank {
public static void main(String[] args) {
Account account=new Account(100,"结婚基金");
Drawing you=new Drawing(account,50,"你");
Drawing girl=new Drawing(account,100,"女朋友");
new Thread(you).start();
new Thread(girl).start();
}
}
//账户
class Account {
public Account(int money, String name) {
this.money = money;
this.name = name;
}
int money;
String name;
}
//银行:模拟取款
class Drawing extends Thread {
public Drawing(Account account, int drawingMoney, String name) {
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
Account account;//账户
//取了多少钱
int drawingMoney;//取了多少钱
int money;//还有多少钱
@Override
public void run() {
//判断是否有钱
if (account.money - drawingMoney <= 0) {
System.out.println("钱不够了,暂时取不了");
return;
}
account.money = account.money - drawingMoney;
money = money + drawingMoney;
System.out.println(account.name + "卡内余额为:" + account.money);
System.out.println(account.name + "手里的钱:" + money);
}
}
要确保同步可以加synchronized 能保持同步方法(保持数据方法)
九:死锁
概念:多个线程各自占有一些共享资源,并且互相等待其它线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情况。某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁"问题。
注意:产生死锁的四个必要条件:
1,互斥条件:一个资源每次只能被一个进程使用
2,请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
3,不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
4,循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
/**
* 死锁:多个线程互相抱着互相的资源,然后形成思索
*/
public class DeadLockTest01 {
public static void main(String[] args) {
MakeUp makeUp=new MakeUp(0,"灰姑娘");
MakeUp makeUp1=new MakeUp(1,"花姑娘");
new Thread(makeUp).start();
new Thread(makeUp1).start();
}
}
//口红
class Lipstick {
}
//镜子
class Mirror {
}
//化装
class MakeUp extends Thread {
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
int chose;//选择
String grilName;
MakeUp(int choise,String girlName){
this.chose =choise;
this.grilName=girlName;
}
/**
* 化装方法
* @throws InterruptedException
*/
private void MakeUp() throws InterruptedException {
if(chose==0){
synchronized(lipstick){
System.out.println(grilName+ "拿到了口红的锁");
Thread.sleep(1000);
}
synchronized (mirror){
System.out.println(grilName+"拿到了镜子的锁");
}
}else{
synchronized(mirror){
System.out.println(grilName+ "拿到了镜子的锁");
Thread.sleep(1000);
}
synchronized (lipstick){
System.out.println(grilName+"拿到了口红的锁");
}
}
}
@Override
public void run() {
super.run();
try {
MakeUp();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
以上代码中是正常不死锁的关系 如果需要查看死锁则把上锁的代码嵌套如下:
private void MakeUp() throws InterruptedException {
if (chose == 0) {
synchronized (lipstick) {
System.out.println(grilName + "拿到了口红的锁");
Thread.sleep(1000);
synchronized (mirror) {
System.out.println(grilName + "拿到了镜子的锁");
}
}
} else {
synchronized (mirror) {
System.out.println(grilName + "拿到了镜子的锁");
Thread.sleep(1000);
synchronized (lipstick) {
System.out.println(grilName + "拿到了口红的锁");
}
}
}
}
十:Lock(锁)
1,从JDK5.0开始,java提供了更强大的线程同步机制--通过显式定义同步对象来实现同步。同步锁使用Lock对象充当。
2,java.util.concurrent.locks.lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应该先获得Lock对象
3,ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁和释放锁。
/**
* 测试Lock锁
*/
public class TestLock {
public static void main(String[] args) {
TestLock2 testLock2 = new TestLock2();
new Thread(testLock2).start();
new Thread(testLock2).start();
new Thread(testLock2).start();
}
}
class TestLock2 implements Runnable {
int tickNums = 10;
//定义Lock
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
lock.lock();//加锁
if (tickNums > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(tickNums--);
} else {
break;
}
} finally {
lock.unlock();
}
}
}
}
十一:synchronized和Lock锁的对比
1,Lock是显式锁(手动开启和关闭,别忘记关锁),synchronized是隐式锁,出了作用域自动释放。
2,Lock只有代码块锁,synchronized有代码块锁和方法锁
3,使用Lock锁,JVM将花费较少的时间来调度线程,性能更好,并且具有更好的扩展性(提供更多的子类)
4,优先使用顺序:Lock->同步代码块(已经进入了方法体,分配了相应资源)->同步方法(在方法体之外)
十二:线程协作(生产者和消费者模式)
应用场景:生产者和消费者问题()
1,假设仓库中只能存放一件商品,生产者将生产出来的商品放入到仓库,消费者将仓库中的商品取走消费。
2,如果仓库中没有商品,则生产者将产品放入仓库,否则停止生产并等待,直接仓库中的产品被消费者取走为止。
3,如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入商品为止。
十二:线程池
/**
* 测试线程池
*/
public class TestPool {
public static void main(String[] args) {
//创建服务,创建线程池
ExecutorService service = Executors.newFixedThreadPool(10);//参数为线程池大小
//执行
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
//关闭连接
service.shutdown();
}
}
class MyThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
先就这样吧。。。。。现在都晚上12点了,后期慢慢回顾!!