多线程之并发基础(三)
线程是轻量级的进程,进程可以说是线程的容器。线程是程序执行的最小单位。使用多线程而不是使用多进程进行并发程序的设计,因为线程的切换和调度成本远远小于进程。
本文知识点目录:
- 线程的状态
- 线程的常见操作
- Daemon线程
- 线程优先级
- wait与notify
线程的状态
在Java程序中,线程有如下状态
NEW(新建): 新的线程刚刚创建,还没有启动。
Runnable(就绪):线程已经调用了start方法,正在被JVM执行的时候,也有可能正在等待某个操作系统的资源。
Blocked(阻塞):有其它线程占着锁不释放,当前线程阻塞与锁
Waiting(等待):表示当前线程需要等待其它线程做出一些特定动作(通知或中断),当调用以下方法时会进入此状态。
Object.wait()没有时间参数
Thread.join()没有超时参数
LockSupport.park方法Timed Waiting(等待并计时):指定时间waiting,一边等,一边计时,一般调用如下方法可以j进入此状态。
Thread.sleep
Object.wait()带有参数
Thread.join ()带有超时参数
LockSupport.parkNanos
LockSupport.parkUntilTerminated(终止) 线程完成了它的操作,也有可能错误的执行而终止。
线程操作
实现多线程
我们想要实现多线程常用的有两种方法
- 实现Runnable接口
- 继承自Thread线程
java只能单继承,因此如果是采用继承Thread的方法,那么在以后进行代码重构的时候可能会遇到问题,因为你无法继承别的类了。
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
注意这是在多线程情况下, 每个人运行的结果可能都不一样。要想保住线程安全性,还需要额外的操作。
// 案例代码,继承线程变量无法共享 public class ThreadOrRunnable extends Thread { private int count=5; private String name; public ThreadOrRunnable(String name) { this.name=name; } @Override public void run() { // super.run(); for (int i = 0; i < 5; i++) { count--; System.out.println(name + "运行 count=" + count); } } public static void main(String[] args) { ThreadOrRunnable threadOrRunnable = new ThreadOrRunnable("A"); ThreadOrRunnable threadOrRunnableB = new ThreadOrRunnable("B"); threadOrRunnable.start(); threadOrRunnableB.start(); } } //运行结果: A运行 count=4 A运行 count=3 A运行 count=2 A运行 count=1 B运行 count=4 A运行 count=0 B运行 count=3 B运行 count=2 B运行 count=1 B运行 count=0
// 案例 继承自Runnable可以共享count变量 public class RunnableTest implements Runnable{ private int count=15; @Override public void run() { for (int i = 0; i < 5; i++) { count--; System.out.println(Thread.currentThread().getName() + "运行: count=" + count); } } public static void main(String[] args) { RunnableTest runnableTest = new RunnableTest(); new Thread(runnableTest,"A").start(); new Thread(runnableTest,"B").start(); } } // 运行结果 B运行: count=13 A运行: count=13 B运行: count=12 A运行: count=11 B运行: count=10 A运行: count=9 A运行: count=7 A运行: count=6 B运行: count=8 B运行: count=5
Daemon线程
Daemon线程是一种支持型线程,在后台调度及支持性工作。它有两个点需要注意:
- 必须在程序调用start方法之前调用Thread.setDaemon(true)方法才能设置为daemon线程,如果start()之后设置,会报
IllegalThreadStateException
异常 - 在所有前台线程执行完毕的时候,daemon线程会自动销毁。
Thread thread = new Thread(); thread.setDaemon(true); thread.start();
线程优先级
Java中的线程可以有自己的优先级,优先级高的线程在竞争资源时更可能
抢占资源,但是这只是一个概览问题,并不是谁的优先级高,谁就一定先执行。
Thread thread = new Thread(); // MIN_PRIORITY = 1; // NORM_PRIORITY = 5; // MAX_PRIORITY = 10; thread.setPriority(int newPriority) thread.start();
wait与notify
wait和notify方法不是在Thread类中的,而是在Object类中,意味着任何对象都可以调用这两个方法。
当一个线程A调用了obj.wait()方法,那么线程A就会停止继续执行,而是转为waiting状态。一直都其它线程调用obj.notify()方法为止。
注意:
- notify方法是从等待队列中的线程随机选择一个,我们无法保证它唤醒的是那一个。notifyAll()方法会唤醒所有等待的线程。
- object.wait()方法必须包含在synchronized语句中,wait或notify都需要首先获得目标对象的监视器。
Object.wait和Thread.sleep方法都可以让线程等待若干时间,它们哟徐诶区别。
- wait可以被唤醒,sleep不可以
- wait会释放目标对象的锁,而sleep不释放任何锁。
public class WaitNotify { static volatile boolean flag = true; static Object lock = new Object(); static class Wait implements Runnable{ @Override public void run() { synchronized (lock){ while (flag){ System.out.println(Thread.currentThread().getName() + " 时间: " + System.currentTimeMillis()); try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //条件满足的时候完成了工作 System.out.println("flag=" + flag + "任务完成"); } } } static class Notify implements Runnable{ @Override public void run() { synchronized (lock){ System.out.println(Thread.currentThread().getName() + " 持有锁"); lock.notifyAll(); flag = false; try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } synchronized (lock){ System.out.println("再次持有锁"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) throws InterruptedException { Thread waitThread = new Thread(new Wait(),"waitThread"); waitThread.start(); TimeUnit.SECONDS.sleep(1); Thread notifyThread = new Thread(new Notify(),"notifyThread"); notifyThread.start(); } }
//运行结果,调用notify之后,wait状态会变为blocked状态,然后再进入Runnable状态。
Wait与notify可以提炼出等待/通知的经典范式,氛围两部分:
等待方规则:
- 获取对象的锁
- 如果条件不满足,那么调用wait方法,被通知后仍要检查条件
- 条件满足则执行相应的逻辑
通知方规则:
- 获得对象的锁
- 改变条件
- 通知等待对象上的线程。
问题
关于Object.wait与notify,最后再留下来一个问题:
为什么wait与notify方法要在Object类中调用而不是在Thread中?
最后
这次提到了一些Thread的基本概念,线程的状态切换,线程的两个属性,最后提了一下wait与notify方法。下次讲一下线程的常见用法。
参考
- Lifecycle and States of a Thread in Java
- 《Java并发编程的艺术》
- 《Java高并发程序设计》
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
回顾以前的线程安全的类
回顾前面的线程安全问题(看看源码) StringBuffer/StringBuilder Vector Hashtable 以及Collections中的让集合同步的方法 示例代码如下: 1 package cn.itcast_12; 2 3 import java.util.ArrayList; 4 import java.util.Collections; 5 import java.util.Hashtable; 6 import java.util.List; 7 import java.util.Vector; 8 9 public class ThreadDemo { 10 public static void main(String[] args) { 11 // 线程安全的类 12 StringBuffer sb = new StringBuffer(); // 几乎所有的方法都加了synchronized,所以线程安全,但效率低。 13 Vector<String> v = new Vector<String>(); // 几乎所有的方法都加了...
- 下一篇
[Java工具] 关于byte和int的转换
需求 单片机通过Socket发送过来类似 { 0xff,0x0c ,0x80...}的byte数组,根据协议分为unsigned char 和signed char两种类型。需要将int数据转为两个byte发送给单片机,或将单片机发来的2个byte转化为int。 工具程序 /** * 将两个byte数据转化为有符号int * @param high : 高八位 * @param low : 低八位 * @return */ public static int twoByteToSignedInt(byte high,byte low){ return (high << 8) | low; } /** * 将两个byte数据转化为无符号int * @param high : 高八位 * @param low : 低八位 * @return */ public static int twoByteToUnsignedInt(byte high,byte low){ return ((high << 8) & 0xffff) | (low & 0x00...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS关闭SELinux安全模块
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- CentOS8编译安装MySQL8.0.19
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS7,CentOS8安装Elasticsearch6.8.6
- MySQL8.0.19开启GTID主从同步CentOS8