1.程序:是一个静态的概念,一段静态的代码,一般对应于操作系统中的一个可执行的文件,比如:我们要启动酷狗听音乐,则对应酷狗的可执行程序。当我们双击酷狗,则加载程序到内存中,开始执行该程序,于是产生了“进程”。
2.进程:执行中的程序叫做进程,它是一个动态的概念,进程是程序的一次动态执行过程,占用特定的地址空间,现代的操作系统都可以同时启动多个进程。比如:我在敲代码的同时还可以听音乐,看视频,浏览网页呢
3.线程:一个程序内部的一条执行路径。线程是一个连续的控制流程,是操作系统能够进行运行调度的最小单位。**一个进程可以产生多个线程,一个进程中至少有一个线程(主线程,main()),**多个进程可以共享操作系统的资源,同一进程的多个线程也可以共享此进程的某些资源(比如:代码,数据),所以线程又被称为轻量级的进程。
区别 | 进程 | 线程 |
---|---|---|
根本区别 | 作为资源分配的单位 | 调度和执行的单位 |
开销 | 每个进程都有独立的代码和数据空间(进程上下文), 进程间的切换会又有较大的开销 | 线程可以看成是轻量级的进程,同一类线程共代码和数据空间,每一个线程有独立的运行栈和1程序计数器(PC),线程切换的开销小 |
所处环境 | 在操作系统中能同时运行多个任务(程序) | 在同一应用程序中有多个顺序流同时执行 |
分配内存 | 系统在运行的时候 会为每个进程分配不同的内存区域 | 除了CPU之外,不会为线程分配内存(线程所使用的资源是它所属的进程的资源),线程只能共享资源 |
包含关系 | 没有线程的进程是可以被看作单线程的,如果一个进程内拥有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成 | 线程是进程的一部分,所以线程有的时候被称为轻权进程或者轻量级进程 |
注意:
1.很多多线程都是模拟出来的,真正的多线程是指有多个CPU,即多核,如服务器,如果是模拟出来的多线程,即一个CPU的情况下,在同一个时间点,CPU只能执行一个代码,因为切换的很快,所以就有同时执行的错觉
2.线程就是独立的执行路径
3.在程序运行时,即使没有自己创建线程,后台也存在多个线程,如gc线程,主线程
4.main()称之为主线程,为系统的入口点,用于执行整个程序;
5.在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的;
6.对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制
7.每个线程在自己的工作内存交互,加载和存储主内存控制不当会造成数据不一致
4.多进程是指操作系统能同时运行多个任务(程序)。
5.多线程是指在同一程序中有多个顺序流在执行。
1.继承Thread类;
2.实现Runnable接口;
3.实现Callable接口
1. 在Java中负责实现线程功能的类是java.lang.Thread 类。
2. 可以通过创建 Thread的实例来创建新的线程。
3. 每个线程都是通过某个特定的Thread对象所对应的方法run( )来完成其操作的,方法run( )称为线程体。
4. 通过调用Thread类的start()方法来启动一个线程。
继承Thread类实现多线程的步骤:
1.创建:继承Thread+重写run
2.启动:创建子类对象+start
package Thread;
/**
* 创建线程方式一
* 1.创建:继承Thread+重写run
* 2.启动:创建子类对象+start
*/
public class StartThread extends Thread{
/**
* 线程入口点
*/
//重写run()方法
public void run(){
for(int i=0;i<20;i++){
System.out.println("王德法");
}
}
public static void main(String[] args) {
//创建子类对象
StartThread st=new StartThread();
//启动
st.start();//不保证立即运行,由cpu调用
//st.run();//普通方法调用
for(int i=0;i<20;i++){
System.out.println("你要干嘛");
}
}
}
多运行几次观察运行结果的不同,多次运行后会发现,运行的结果不是固定的,从程序运行的结果可以发现,多线程程序是乱序执行。因此,只有乱序执行的代码才有必要设计为多线程。,线程的运行顺序由CPU决定,人为无法干预
注意:
1.如果用st.run();直接调用run方法属于普通方法的调用,属于主线程
2.注意多线程的启动位置,启动从调用start开始
3。Java单继承,继承了Thread类后不能再继承其他类,同样,继承了其他类也不可以继承Thread类
4.start()方法的调用后并不是立即执行多线程代码,而是使得该线程变为可运行态,什么时候运行是由操作系统决定的。
实现Runnable接口实现多线程的步骤
**1.创建:实现Runnable+重写run
ackage Thread;
/**
* 创建线程方式二
* 1.创建:实现Runnable+重写run
* 2.启动:创建实现类对象+Thread对象+start
*
* 推荐使用该种创建线程的方法:避免单继承的局限性,优先使用接口
* 方便共享资源
*/
public class StartRun implements Runnable{
/**
* 线程的入口点
*/
//重写run方法
@Override
public void run() {
for(int i=0;i<20;i++){
System.out.println("王德发");
}
}
public static void main(String[] args) { //主线程
//创建实现类对象
StartRun sr=new StartRun();
//创建Thread对象
Thread td=new Thread(sr);
// new Thread(new StartRun());//如果一个对象只使用一次,我们可以使用使用匿名,即可以不用声明引用
// //启动
td.start();
for(int i=0;i<20;i++){
System.out.println("你要干嘛");
}
}
}
注意:
1.Thread类实际上也是实现了Runnable接口的类。
2.在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用Thread对象的start()方法来运行多线程代码。
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
实现Runnable接口比继承Thread类所具有的优势:
1):适合多个相同的程序代码的线程去处理同一个资源
2):可以避免java中的单继承的限制
3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
4):线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类
注意:main方法其实也是一个线程。在java中所以的线程都是同时启动的,至于什么时候,哪个先执行,完全看谁先得到CPU的资源。
在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个jVM实习在就是在操作系统中启动了一个进程。
转载声明:https://blog.csdn.net/Evankaka/article/details/44153709
package Thread;
/**
* 共享资源,并发(线程安全)
* 使用多线程模拟抢票
*/
public class Web12306 implements Runnable{
//初始票数
private int ticketNums=99;
@Override
//重写run方法
public void run() {
while(true){
if(ticketNums<0){
break;
}
//使用try---catch的原因是:run()方法不能对丢出异常,同时run方法没有返回值
// try {
// Thread.sleep(200);//模拟网络延迟,
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
System.out.println(Thread.currentThread().getName()+ticketNums--);
//Thread.currentThread().getName()当前线程的名字,谁运行它代表谁
}
}
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName());//放在这里就是指main主线程
//创建实现类对象
//一份资源
Web12306 web=new Web12306();
//创建代理类对象
//构造器里是可以传名称的,方便我们区分不同的线程
// 多个代理
new Thread(web,"码农").start();
new Thread(web,"码畜").start();
new Thread(web,"码璜").start();
}
}
注意:
1.一份资源可以供多个线程分配
2.sleep(long millis) 线程睡眠 millis 毫秒,sleep方法只能让当前线程睡眠。
在run()方法中使用sleep时,对sleep使用try—catch的原因:run()方法不能抛出异常,且run()方法没有返回值
3.Thread的构造器是可以传名称的,便于我们区分不同的线程
4.Thread.currentThread().getName()获取当前线程的名字,谁运行它就代表谁
package Thread;
//模拟龟兔赛跑
public class Racer implements Runnable{
private String winner;//胜利者
@Override
public void run() {
for(int steps=1;steps<=100;steps++){
//这里让rabbit的线程睡眠,即模拟兔子睡觉的过程
// if (Thread.currentThread().getName().equals("rabbit" )&& steps % 10 == 0){
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
System.out.println(Thread.currentThread().getName()+"----"+steps);
//比赛是否结束
boolean flag=gameOver(steps);
if(flag){
break;
}
}
}
private boolean gameOver(int steps) {
if (winner != null) {
return true;
} else {
if (steps == 100) {
winner = Thread.currentThread().getName();
System.out.println(winner + "===>" + winner);
return true;
}
}
return false;
}
public static void main(String[] args) {
//创建实现类对象
Racer racer=new Racer();
new Thread(racer,"tortoise").start();
new Thread(racer,"rabbit").start();
}
}
静态代理的要则:
1、实现相同接口,2、在静态代理类中注入被代理对象,3、在静态代理类中实现接口方法,4、调用被代理对象的方法。
package Thread;
/**
*静态代理
* 1.真实角色
* 2.代理角色
*/
public class StaticProxy {
public static void main(String[] args) {
new WeddingCompany(new you()).happyMarry();
//new Thread(线程对象).start();
//Thread就相当于代理角色,线程对象就相当于真实角色,他们实现了相同的接口
}
}
//结婚的接口
interface Marry{
void happyMarry();
}
//真实角色
class you implements Marry{
@Override
public void happyMarry() {
System.out.println("老牛终于啃到了嫩草");
}
}
//代理角色
class WeddingCompany implements Marry{
//在静态代理中注入被代理的对象
private Marry target;
public WeddingCompany(Marry target){
this.target=target;
}
@Override
//在静态代理类中实现接口的方法
public void happyMarry() {
ready();
this.target.happyMarry();//调用被代理对象的方法
after();
}
private void ready(){
System.out.println("要啥啥也没有");
}
private void after(){
System.out.println("都拿去卖破烂");
}
}java
转载声明
lambda表达式是一个匿名函数,即没有函数名的函数
java中lambda表达式本质上也是匿名函数,但其实现原理区别于一般的匿名类中的匿名函数实现
1.简化代码,引入强大的类型推断和方法引用特性,简单的功能甚至只需要一行代码解决,解放匿名类的束缚
2.把功能作为参数向下传递,为函数式编程提供了支持
3.但是lambda表达式只适用于只有一个方法的接口,如果接口中有多个方法则就不能使用lambda表达式
接口类型 接口实例临时变量 = (方法参数类型X 方法参数类型X临时变量 , 方法参数类型Y 方法参数类型Y临时变量…) -> { 方法体… return 接口抽象方法返回值对应类型类型实例;}
一个lambda表达式可以有五部分组成:
返回值:接口类型以及接口类型对应的临时实例变量
等号:=
方法参数列表:一般由中括号()包裹,格式是(类型1 类型1的临时变量,…,类型N 类型N的临时变量),在方法没有重载可以明确推断参数类型的时候,参数类型可以省略,只留下临时变量列表。特殊地,空参数列表用()表示,如果参数只有一个,可以省略()。
箭头:->。
方法体:一般由花括号{}包裹,格式是{方法逻辑… return 函数式接口方法返回值类型的值;},有几点需要注意:
如果方法体是空实现,用{}表示,如Runnable runnable = () -> {};。
如果函数式接口抽象方法的返回值为void类型,则不需要return关键字语句,如Runnable runnable = () -> {int i=0; i++;};。
如果函数式接口抽象方法的方法体仅仅包含一个表达式,则不需要使用{}包裹,如Runnable runnable = () -> System.out.println(“Hello World!”);。
先通过例子来看看lambda表达式是怎么实际操作的
```java
package Thread;
public class Lambda04 {
package Thread;
public class Lambda04 implements Runnable{
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println("一边学习lambda");
}
}
public static void main(String[] args) {
new Thread(new Lambda04()).start();
}
}
package Thread;
public class Lambda04 {
public static void main(String[] args) {
new Thread(()->{
System.out.println("一边学习lambda");
}).start();
}
}
将这两个代码进行对比,两种操作实现是一样的,但是第二种明显更加简洁,这就是lambda表达式
package Thread;
/**
* lambda表达式,简化线程(用一次)的使用
* 避免匿名内部类定义过多
* 其实质属于函数式编程的概念
*/
public class Lambda {
//一个类如果只用一次,为了提升它的性能,我们可以把它作内部类
//内部类的好处是内部类随着外部类的使用进行加载,如果外部类不使用就不会对这个类进行加载
//使用静态内部类,创建一个静态内部类的对象不需要依赖其外部对象
static class Test implements Runnable {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("一边听歌");
}
}
}
public static void main(String[] args) {
//new Thread(new Test()).start();
//局部内部类,定义在方法或者代码块的内部
class Test2 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("一边听歌");
}
}
}
//new Thread(new Test2()).start();
//匿名内部类,必须借助接口或者父类
new Thread(new Runnable(){
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("一边听歌");
}
}
}).start();
//JDK8简化lambda
//JDK8中lambda会自己去推导它是属于Thread接口Runnable的实现类,重写的run方法
//lambda在推导的时候只能是一个方法,如果接口里有多个方法就推导不了
//lambda推导必须存在类型
new Thread(()->{
for (int i = 0; i < 20; i++) {
System.out.println("一边听歌");
}
}).start();
}
}
用new关键字建立一个线程对象后,该线程对象就处于新生状态。处于新生状态的线程有自己的内存空间,通过调用start方法进入就绪状态。
处于就绪状态的线程已经具备了运行条件,但是还没有被分配到CPU,处于“线程就绪队列”,等待系统为其分配CPU。就绪状态并不是执行状态,当系统选定一个等待执行的Thread对象后,它就会进入执行状态。一旦获得CPU,线程就进入运行状态并自动调用自己的run方法。有4中原因会导致线程进入就绪状态:
1. 新建线程:调用start()方法,进入就绪状态;
2. 阻塞线程:阻塞解除,进入就绪状态;
3. 运行线程:调用yield()方法,直接进入就绪状态;
package Thread;
/**
* yield 礼让线程,暂停线程,直接进入就绪状态,而不是阻塞状态
*/
public class YieldDemo01 implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"--->start");
Thread.yield();//礼让:礼让不一定礼让成功:如果成功CPU将会调配其他线程,若CPU又重写调配自己,则相当于礼让没成功
System.out.println(Thread.currentThread().getName()+"--->end");
}
public static void main(String[] args) {
YieldDemo01 yd=new YieldDemo01();
new Thread(yd,"a").start();
new Thread(yd,"b").start();
}
}
package Thread;
public class YieldDemo02 {
public static void main(String[] args) {
new Thread(()->{
for(int i=0;i<100;i++){
System.out.println("yield"+i);
}
}).start();
for(int i=0;i<100;i++){
if(i%20==0){
Thread.yield();
}
System.out.println("main"+i);
}
}
}
5. 运行线程:JVM将CPU资源从本线程切换到其他线程。
在运行状态的线程执行自己run方法中的代码,直到调用其他方法而终止或等待某资源而阻塞或完成任务而死亡。如果在给定的时间片内没有执行结束,就会被系统给换下来回到就绪状态。也可能由于某些“导致阻塞的事件”而进入阻塞状态。
阻塞指的是暂停一个线程的执行以等待某个条件发生(如某资源就绪)
有4种原因会导致阻塞:
Sleep睡眠暂停
1. 执行sleep(int millsecond)方法,使当前线程休眠,进入阻塞状态。当指定的时间到了后,线程进入就绪状态。
前面的模拟抢票和龟兔赛跑都涉及有sleep
sleep模拟倒计时
package Thread;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* sleep倒数时间
*/
public class BlockedSleep {
public static void main(String[] args) throws InterruptedException {
Date endTime=new Date(System.currentTimeMillis()+1000*10);
long end=endTime.getTime();
while(true){
System.out.println(new SimpleDateFormat("mm:ss").format(endTime));
Thread.sleep(1000);
endTime=new Date(endTime.getTime()-1000);
if((end-10000)>endTime.getTime()){
break;
}
}
}
}
2. 执行wait()方法,使当前线程进入阻塞状态。当使用nofity()方法唤醒这个线程后,它进入就绪状态。
3. 线程运行时,某个操作进入阻塞状态,比如执行IO流操作(read()/write()方法本身就是阻塞的方法)。只有当引起该操作阻塞的原因消失后,线程进入就绪状态。
4. join()线程联合: 当某个线程等待另一个线程执行结束后,才能继续执行时,使用join()方法。
join()写在哪个线程的线程体中,哪一个线程就会被阻塞,其他线程不受影响;
thread.join(); 不指定当前线程wait的时间,这会导致当前线程一直阻塞至thread执行完毕
thread.join(long time); 指定当前线程wait时间,使被阻塞线程等待设定的时间后解除阻塞或者在时间结束前另外的一个线程执行结束时就可以解除阻塞
先简单理解一下join的作用
package Thread;
/**
* join 合并线程,插队线程
* Join写在哪个线程的线程体中,哪个线程就会被阻塞
*/
public class BlockedJoin {
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(()->{
for(int i=0;i<100;i++){
System.out.println("yield"+i);
}
});
t.start();
for(int i=0;i<100;i++){
if(i==20){
t.join(); //插队线程,此时main线程被阻塞
//join也可以传时间,时间到了,如果这个线程还没有完成,CPU就会重新调度
}
System.out.println("main"+i);
}
}
}
使用join模拟爸爸等儿子买烟回来
package Thread;
/**
* 用join插队线程模拟爸爸让儿子买烟
*/
public class BlockedJoin01 {
public static void main(String[] args) {
System.out.println("老爸让儿子买烟的故事");
Thread tt=new Thread(new Father());
tt.start();
System.out.println("老爸接过烟,把零钱给了儿子");
}
}
class Father extends Thread{
@Override
public void run() {
System.out.println("生活压力好大,来一根烟吧");
System.out.println("没烟了,让儿子去买一盒中华");
Thread t=new Thread(new Son());
t.start();
try {
t.join();//写在了Father的线程体中,Father被阻塞了
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("孩子走丢了,老爸找儿子去了。。。。");
}
}
}
class Son extends Thread{
@Override
public void run() {
System.out.println("接过老爸的钱出去了。。。。");
System.out.println("路边有一个游戏厅,玩了10秒");
for(int i=1;i<=10;i++){
try {
Thread.sleep(1000);
System.out.println(i+"秒过去了");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("赶紧去买烟");
System.out.println("拿着中华回去啦");
}
}
某一个线程的线程体中使用了Join,那么该线程就会被阻塞,但是其他线程不受影响,见代码
package Thread;
public class BlockedJoin02 implements Runnable{
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(new BlockedJoin02(),"a");
Thread t2=new Thread(new BlockedJoin02(),"b");
t1.start();
t2.start();
for(int i=0;i<10;i++){
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+"过去了"+i);
if(i==5){
t1.join();
}
}
}
@Override
public void run() {
System.out.println("开始执行-->"+Thread.currentThread().getName());
for(int i=0;i<10;i++){
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+"过去了"+i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("结束执行-->"+Thread.currentThread().getName());
}
}
死亡状态是线程生命周期中的最后一个阶段。线程死亡的原因有两个。一个是正常运行的线程完成了它run()方法内的全部工作; 另一个是线程被强制终止,如通过执行stop()或destroy()方法来终止一个线程(注:stop()/destroy()方法已经被JDK废弃,不推荐使用)。
当一个线程进入死亡状态以后,就不能再回到其它状态了。
package Thread;
/**终止线程
* 1.线程正常执行完毕
* 2.外部干涉-->加入标识
*/
public class TerminateThread implements Runnable{
//加入标识,标记线程是否可以运行
private boolean flag=true;
private int i=0;
private String name;
public TerminateThread(String name){
this.name=name;
}
@Override
public void run() {
//关联标识, true -->运行 false-->停止
while(flag){
System.out.println(name+"-->"+i);
i++;
}
}
//对外提供方法改变标识
public void terminate(){
this.flag=false;
}
public static void main(String[] args) {
TerminateThread tt=new TerminateThread("猪八戒");
Thread t=new Thread(tt);
t.start();
for(int i=0;i<99;i++){
if(i==88){
tt.terminate();
}
System.out.println("main-->"+i);
}
}
}
NEW:一个尚未启动的线程的状态。也称之为初始状态、开始状态。
RUNNABLE:一个可以运行的线程的状态,可以运行是指这个线程已经在JVM中运行了,但是有可能正在等待其他的系统资源。也称之为就绪状态、可运行状态。
BLOCKED:一个线程因为等待监视锁而被阻塞的状态。也称之为阻塞状态。
WAITING:一个正在等待的线程的状态。也称之为等待状态。造成线程等待的原因有三种,分别是调用Object.wait()、join()以及LockSupport.park()方法。处于等待状态的线程,正在等待其他线程去执行一个特定的操作。例如:因为wait()而等待的线程正在等待另一个线程去调用notify()或notifyAll();一个因为join()而等待的线程正在等待另一个线程结束。
TIMED_WAITING:一个在限定时间内等待的线程的状态。也称之为限时等待状态。造成线程限时等待状态的原因有五种,分别是:Thread.sleep(long)、Object.wait(long)、join(long)、LockSupport.parkNanos(obj,long)和LockSupport.parkUntil(obj,long)。
TERMINATED:一个完全运行完成的线程的状态。也称之为终止状态、结束状态。
package Thread;
/**
* 观察线程的状态
*
*/
public class AllStates {
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(()->{
for(int i=0;i<10;i++){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("???");
}
});
//观察状态
Thread.State state=t.getState();
System.out.println(state);
t.start();
state=t.getState();//RUNNABLE
System.out.println(state);
while(state!=Thread.State.TERMINATED){
// int num=Thread.activeCount();//活动的线程
// System.out.println(num);
Thread.sleep(200);
state=t.getState();
System.out.println(state);
// if(num==2){
// break;
// }
}
}
}
处于就绪状态的线程,会进入“就绪队列”等待JVM来调配。
线程的优先级用数字表示,范围从1到10,一个线程的缺省优先级是5。
使用下列方法获得或设置线程对象的优先级。
int getPriority();
void setPriority(int newPriority);
注意:优先级低只是意味着获得调度的概率低。并不是绝对先调用优先级高的线程后调用优先级低的线程。
package Thread;
/**
* 线程的优先级:范围从1到10,默认是5
* NORM_PRIORITY 5
* MIN_PRIORITY 1
* MAX_PRIORITY 10
* :优先级低只是意味着获得调度的概率低。并不是绝对先调用优先级高的线程后调用优先级低的线程。
*/
public class Priority {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getPriority());
Thread t1=new Thread(new MyPriority(),"李宁");
Thread t2=new Thread(new MyPriority(),"回力");
Thread t3=new Thread(new MyPriority(),"安踏");
Thread t4=new Thread(new MyPriority(),"NIKE");
Thread t5 =new Thread(new MyPriority(),"puma");
Thread t6 =new Thread(new MyPriority(),"adidas");
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.MAX_PRIORITY);
t3.setPriority(Thread.MAX_PRIORITY);
t4.setPriority(Thread.NORM_PRIORITY);
t5.setPriority(Thread.MIN_PRIORITY);
t6.setPriority(Thread.MIN_PRIORITY);
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
t6.start();
}
}
class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
Thread.yield();
}
}
在Java中有两类线程:User Thread(用户线程)****、Daemon Thread(守护线程)
用个比较通俗的比如,任何一个守护线程都是整个JVM中所有非守护线程的保姆:
只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。
Daemon的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC (垃圾回收器),它就是一个很称职的守护者。
User和Daemon两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果 User Thread已经全部退出运行了,只剩下Daemon Thread存在了,虚拟机也就退出了。 因为没有了被守护者,Daemon也就没有工作可做了,也就没有继续运行程序的必要了。
补充说明:
1.
定义:守护线程–也称“服务线程”,在没有用户线程可服务时会自动离开。
优先级:守护线程的优先级比较低,用于为系统中的其它对象和线程提供服务。
设置:通过setDaemon(true)来设置线程为“守护线程”;将一个用户线程设置为
守护线程的方式是在 线程对象创建 之前 用线程对象的setDaemon方法。
2.
example: 垃圾回收线程就是一个经典的守护线程,当我们的程序中不再有任何运行的Thread,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是JVM上仅剩的线程时,垃圾回收线程会自动离开。它始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。
3.
生命周期:守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。也就是
说守护线程不依赖于终端,但是依赖于系统,与系统“同生共死”。那Java的守护线程是什么样子的呢。当JVM中所有的线程都是守护线程的时候,JVM就可以退出了;如果还有一个或以上的非守护线程则JVM不会退出。
守护线程并非只有虚拟机内部提供,用户在编写程序时也可以自己设置守护线程
package Thread;
/**
* 守护线程:为用户线程服务,jvm停止不用等待线程执行完毕
* 用户线程:jvm等待用户线程执行完毕
*/
public class DaemonTest {
public static void main(String[] args) {
God god=new God();
You1 you=new You1();
Thread t=new Thread(god);
t.setDaemon(true);//将用户线程设置为守护线程
t.start();
new Thread(you).start();
}
}
class You1 implements Runnable{
@Override
public void run() {
for(int i=0;i<365*100;i++){
System.out.println("Happy life");
}
System.out.println("Let me go die");
}
}
class God implements Runnable{
@Override
public void run() {
for(;true;){
System.out.println("bless you");
}
}
}
package Thread;
/**
* 线程的常用方法
*
*/
public class InfoTest {
public static void main(String[] args) throws InterruptedException {
System.out.println(Thread.currentThread().isAlive());
System.out.println(Thread.currentThread().getPriority());
Thread t=new Thread(new DogHead("战斗机"));
t.setName("狗头");
System.out.println(t.getName());
t.setPriority(10);
t.start();
Thread.sleep(1000);
System.out.println(t.isAlive());
}
}
class DogHead implements Runnable{
private String name;
public DogHead(String name){
this.name=name;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"--->"+name);
}
}
1.什么是线程不安全和线程不安全?
假设A和B同时去不同ATM上取同一张卡的1000块钱,如果是线程不安全,那么A和B可以同时取到1000块钱(两人赚大发啦),而如果线程安全呢,就只有一个人能取出来1000块钱。
线程安全是指多个线程在执行同一段代码的时候采用加锁机制,使每次的执行结果和单线程执行的结果都是一样的,不存在执行结果的二义性。
线程不安全就是不提供加锁机制保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据
2.是什么决定的线程安全问题呢?
线程安全问题都是由全局变量及静态变量引起的。
若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
线程不安全:模拟抢票
package test00;
/**
*拿票线程不安全:数据存在复数,相同的情况
* 当我们对数据进行读和改的时候就要进行控制
*/
public class UnsafeTest01 {
public static void main(String[] args) {
UnsafeWeb12306 uw=new UnsafeWeb12306();
Thread t1=new Thread(uw,"码农");
Thread t2=new Thread(uw,"码畜");
Thread t3=new Thread(uw,"码煌");
t1.start();
t2.start();
t3.start();
}
}
class UnsafeWeb12306 implements Runnable{
private int numTickets=10;
private boolean flag=true;
@Override
public void run() {
while(flag){
test();
}
}
public void test(){
if(numTickets<0){
flag=false;
return ;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+numTickets);
numTickets--;
}
}
线程不安全:模拟取钱
package test00;
/**
* 线程不安全:取钱
*/
public class UnsafeTest02 {
public static void main(String[] args) {
Account account=new Account(100,"结婚礼金");
Drawing you=new Drawing(account,80,"悲伤的你");
Drawing girlfriend=new Drawing(account,70,"happy的女朋友");
you.start();
girlfriend.start();
}
}
//账户
class Account{
int money;//账户金额
String name;//账号名称
public Account(int money,String name){
this.money=money;
this.name=name;
}
}
//模拟提款机提款
class Drawing extends Thread{
Account account;//账户
int drawingMoney;//需要取的钱数
int packetTotal;
public Drawing(Account account,int drawingMoney,String name){
super(name);
this.account=account;
this.drawingMoney=drawingMoney;
}
public void run(){
if(account.money-drawingMoney<0){
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money -=drawingMoney;
packetTotal +=drawingMoney;
System.out.println(this.getName()+"-->账户用户余额为"+account.money);
System.out.println(this.getName()+"-->口袋里的钱"+packetTotal);
}
}
线程不安全:操作数组
package test00;
import java.util.ArrayList;
import java.util.List;
/**
* 线程不安全:操作容器
*/
public class UnsafeTest03 {
public static void main(String[] args) {
List<String> list=new ArrayList<String>();
for(int i=0;i<10000;i++){
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
System.out.println(list.size());
}
}
1.并发:同一个对象多个线程同时操作;
2.线程同步其核心就在于一个“同”。所谓“同”就是协同、协助、配合,“同步”就是协同步调昨,也就是按照预定的先后顺序进行运行,即“你先,我等, 你做完,我再做”。
线程同步,就是当线程发出一个功能调用时,在没有得到结果之前,该调用就不会返回,其他线程也不能调用该方法。
在多线程编程里面,一些较为敏感的数据时不允许被多个线程同时访问的,使用线程同步技术,确保数据在任何时刻最多只有一个线程访问,保证数据的完整性。
由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突的问题。Java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问造成的这种问题。
由于我们可以通过 private 关键字来保证数据对象只能被方法访问,所以我们只需针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种用法:synchronized 方法和 synchronized 块。
同步方法:给一个方法增加synchronized修饰符之后就可以使它成为同步方法,这个方法可以是静态方法和非静态方法,但是不能是抽象类的抽象方法,也不能是接口中的接口方法。
通过在方法声明中加入 synchronized关键字来声明,语法如下:
public synchronized void accessVal(int newVal);
安全的抢票
package Thread;
/**
* 线程安全:在并发时保证数据的正确性,效率尽可能高
* synchronized同步方法
*/
public class SynTest01 {
public static void main(String[] args) {
SafeWeb12306 uw=new SafeWeb12306();
Thread t1=new Thread(uw,"码农");
Thread t2=new Thread(uw,"码畜");
Thread t3=new Thread(uw,"码煌");
t1.start();
t2.start();
t3.start();
}
}
class SafeWeb12306 implements Runnable{
private int numTickets=100;
private boolean flag=true;
@Override
public void run() {
while(flag){
test();
}
}
public synchronized void test(){
if(numTickets<0){
flag=false;
return ;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+numTickets);
numTickets--;
}
}
synchronized 方法的缺陷:若将一个大的方法声明为synchronized 将会大大影响效率。Java 为我们提供了更好的解决办法,那就是 synchronized 块。 块可以让我们精确地控制到具体的“成员变量”,缩小同步的范围,提高效率。
即有synchronized关键字修饰的语句块。 被该关键字修饰的语句块会自动被加上内置锁,从而实现同步
代码如:
synchronized(object){
}
注:同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。
安全的存钱取钱过程
package Thread;
/**
* 同步块:目标更明确
*/
public class SynTest02 {
public static void main(String[] args) {
SafeAccount account=new SafeAccount(1000,"结婚礼金");
SafeDrawing you=new SafeDrawing(account,80,"悲伤的你");
SafeDrawing girlfriend=new SafeDrawing(account,70,"happy的女朋友");
you.start();
girlfriend.start();
}
}
//账户
class SafeAccount{
int money;//账户金额
String name;//账号名称
public SafeAccount(int money,String name){
this.money=money;
this.name=name;
}
}
//模拟提款机提款
class SafeDrawing extends Thread{
SafeAccount account;//账户
int drawingMoney;//需要取的钱数
int packetTotal;
public SafeDrawing(SafeAccount account,int drawingMoney,String name){
super(name);
this.account=account;
this.drawingMoney=drawingMoney;
}
public void run(){
//假如账户里没有钱了,每一个线程过来后就不用在去synchronized那里等待询问这个锁有没有,然后CPU都要再切换一下
//就相当于酒店没有房间了,然后住酒店的人总要去询问一下还有没有房间的钥匙,店家总要回复一下没有了
//在这里直接声明没有房间了,线程也就不会再去询问,提高性能
if(account.money<=0){
return;
}
//同步块:锁定具体的对象:
// 目标锁定account
synchronized (account){
if(account.money-drawingMoney<0){
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money -=drawingMoney;
packetTotal +=drawingMoney;
System.out.println(this.getName()+"-->账户用户余额为"+account.money);
System.out.println(this.getName()+"-->口袋里的钱"+packetTotal);
}
}
}
“synchronized (account)” 意味着线程需要获得account对象的“锁”才有资格运行同步块中的代码。 Account对象的“锁”也称为“互斥锁”,在同一时刻只能被一个线程使用。A线程拥有锁,则可以调用“同步块”中的代码;B线程没有锁,则进入account对象的“锁池队列”等待,直到A线程使用完毕释放了account对象的锁,B线程得到锁才可以开始调用“同步块”中的代码。
安全的操作容器
package Thread;
import java.util.ArrayList;
import java.util.List;
public class SynTest03 {
public static void main(String[] args) {
List<String> list=new ArrayList<String>();
for(int i=0;i<10000;i++){
new Thread(()->{
synchronized (list) {
list.add(Thread.currentThread().getName());
}
}).start();
}
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
快乐影院1.0
package Thread;
public class HappyCinema01 {
public static void main(String[] args) {
Cinema cinema=new Cinema(2,"HPU");
new Thread(new Customer(cinema,2),"洁洁").start();
new Thread(new Customer(cinema, 1),"其其").start();
}
}
class Cinema{
int available;//可购买的票
String name;//影院名称
public Cinema(int available,String name){
this.name=name;
this.available=available;
}
//购票
public boolean bookTickets(int seat){
System.out.println("可用座位为"+available+"个");
if(available<seat){
return false;
}
available -=seat;
return true;
}
}
class Customer implements Runnable{
Cinema cinema;//购票的影院
int seat;//需要购买的票数
public Customer(Cinema cinema,int seat){
this.seat=seat;
this.cinema=cinema;
}
@Override
public void run() {
synchronized ((Integer)seat) {
boolean flag = cinema.bookTickets(seat);//判断能否购票是否成功
if (flag) {
System.out.println("出票成功" + Thread.currentThread().getName() + "-->位置为" + seat);
} else {
System.out.println("出票失败,位置不够");
}
}
}
}
输出结果;
可用座位为2个
出票成功洁洁-->位置为2
可用座位为0个
出票失败,位置不够
多次运行结果不唯一
快乐影院2.0
package Thread;
import java.util.ArrayList;
import java.util.List;
public class HappyCinema02 {
public static void main(String[] args) {
//可用位置
List<Integer> available=new ArrayList<Integer>();
available.add(1);
available.add(2);
available.add(4);
available.add(5);
available.add(7);
//创建影院
HappyCinema cinema=new HappyCinema(available, "HPU");
//创建顾客需要的位置
List<Integer> seats1=new ArrayList<Integer>();
seats1.add(1);
seats1.add(2);
List<Integer> seats2=new ArrayList<Integer>();
seats2.add(4);
seats2.add(5);
seats2.add(7);
//创建顾客
new Thread(new HappyCustomer(cinema, seats1),"洁洁").start();
new Thread(new HappyCustomer(cinema, seats2),"其其").start();
}
}
class HappyCinema {
List<Integer> available;//可购买的票
String name;//影院名称
public HappyCinema(List<Integer> available, String name) {
this.name = name;
this.available = available;
}
//购票
public boolean bookTickets(List<Integer> seat) {
System.out.println("欢迎光临"+this.name+"当前可用座位为" + available + "个");
List<Integer> copy = new ArrayList<Integer>();
copy.addAll(available);//拷贝一份available
copy.removeAll(seat);
if (available.size() - seat.size() != copy.size()) {
return false;
}
available = copy;
return true;
}
}
class HappyCustomer implements Runnable {
HappyCinema cinema;//购票的影院
List<Integer> seat;//需要购买的票数
public HappyCustomer(HappyCinema cinema, List<Integer> seat) {
this.seat = seat;
this.cinema = cinema;
}
@Override
public void run() {
synchronized (cinema) {
boolean flag = cinema.bookTickets(seat);//判断能否购票是否成功
if (flag) {
System.out.println("出票成功" + Thread.currentThread().getName() + "-->位置为" + seat);
} else {
System.out.println("出票失败,位置不够");
}
}
}
}
输出结果 (结果不唯一)
欢迎光临HPU当前可用座位为[1, 2, 4, 5, 7]个
出票成功洁洁-->位置为[1, 2]
欢迎光临HPU当前可用座位为[4, 5, 7]个
出票成功其其-->位置为[4, 5, 7]
快乐火车票
package Thread;
public class HappyWeb {
public static void main(String[] args) {
HappyWeb12306 web=new HappyWeb12306(2, "HPU");
new Passenger(web,2,"其其").start();
new Passenger(web,2,"洁洁").start();
}
}
class Passenger extends Thread {
int seat;//需要购买的票数
public Passenger(Runnable target, int seat,String name) {
super(target,name);
this.seat = seat;
}
}
class HappyWeb12306 extends Thread{
int available;//可购买的票
String name;//影院名称
public HappyWeb12306(int available,String name){
this.name=name;
this.available=available;
}
//购票
public boolean bookTickets(int seat){
System.out.println("可用座位为"+available+"个");
if(available<seat){
return false;
}
available -=seat;
return true;
}
public synchronized void run() {
Passenger p=(Passenger) Thread.currentThread();
boolean flag = this.bookTickets(p.seat);//判断能否购票是否成功
if (flag) {
System.out.println("出票成功" + Thread.currentThread().getName() + "-->位置为" + p.seat);
} else {
System.out.println("出票失败,位置不够");
}
}
}
package test00;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
//并发容器
public class SynContainer {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
for (int i = 0; i < 10000; i++) {
new Thread(() -> {
list.add(Thread.currentThread().getName());
}).start();
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
package test00;
/**
* 死锁:过多的同步可能造成相互不释放资源,从而相互等待,一般发生于同步中持有多个对象的锁
*/
public class DeadLock {
public static void main(String[] args) {
MakeUp g1=new MakeUp(0,"洁洁");
MakeUp g2=new MakeUp(1,"程程");
g1.start();
g2.start();
}
}
//口红
class Lipstick{
}
//镜子
class Mirror{
}
//化妆
class MakeUp extends Thread{
static Lipstick lipstick=new Lipstick();
static Mirror mirror=new Mirror();
//选择
int choice;
//名字
String girl;
public MakeUp(int choice,String girl){
this.girl =girl;
this.choice=choice;
}
@Override
public void run() {
markup();
}
//相互持有对方的对象锁-->这样才可能造成死锁
public void markup(){
if(choice==0){
synchronized (lipstick){//获得口红的锁
System.out.println(this.girl+"涂口红");
//1秒后想拥有镜子的锁
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (mirror){
System.out.println(this.girl+"照镜子");
}
}
}else{
synchronized (mirror){//获得镜子的锁
System.out.println(this.girl+"照镜子");
//2秒后想拥有镜子的锁
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lipstick){
System.out.println(this.girl+"涂口红");
}
}
}
}
}
|
方法名 | 作用 |
---|---|
final void wait() | 表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁 |
final void wait(long timeout) | 指定等待的毫秒数 |
final void notifiy | 唤醒一个处于等待状态的线程 |
final void notifyAll() | 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度 |
均是java.lang.Object类的方法。都只能在同步方法或者同步代码块中使用,否则会抛出异常
package Thread;
/**
* 协助模型:生产者消费者实现方式一:管程法
* wait()等待,阻塞,但是不同于sleep()的睡眠,wait()在阻塞线程时也会释放锁
*/
public class CooperationTest01 {
public static void main(String[] args) {
Container container=new Container();
Producer producer=new Producer(container);
Consumer consumer=new Consumer(container);
producer.start();
consumer.start();
}
}
//生产者
class Producer extends Thread{
Container container;
public Producer(Container container){
this.container=container;
}
@Override
public void run() {
for(int i=1;i<=100;i++){
System.out.println("生产第"+i+"馒头");
container.push(new SteamedBun(i));
}
}
}
//消费者
class Consumer extends Thread{
Container container;
public Consumer(Container container){
this.container=container;
}
@Override
public void run() {
for(int i=1;i<=100;i++){
System.out.println("消费第"+container.pop().id+"个馒头");
}
}
}
//缓冲区
class Container {
SteamedBun[] buns=new SteamedBun[10];
int count=0;//计算现在有多少馒头
//存储馒头
public synchronized void push(SteamedBun bun){
//什么时候可以生产:容器中有空间
// 什么时候不能生产:容器中没有空间
//不能生产。只能等待
if(count==buns.length){
try {
this.wait();//线程阻塞,等待有消费者通知你生产,解除阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//存在空间,可以生产
buns[count]=bun;
count++;
this.notifyAll();
}
//获取馒头
public synchronized SteamedBun pop(){
//什么时候可以消费:容器中数据
//没有数据的话只能等待
if(count==0){
try {
this.wait();//线程阻塞,等待生产者通知你消费,解除阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//存在数据可以消费
count--;
SteamedBun bun=buns[count];
this.notifyAll();
return bun;
}
}
//馒头
class SteamedBun{
int id;//给馒头编号
public SteamedBun(int id){
this.id=id;
}
}
在生产者消费者模式中,信号灯,顾名思义,是用来在生产者与消费者之间传递信号的一个旗帜。如在一个生产与消费模式中,当生产者或消费者线程完成自己的工作,等待另一个线程进行时,便会将信号值修改用以告诉另一者:我的事情做完了,该你了。而另一者获取信号的变化后便会做出对应的行为。在这个过程中,信号值一直被反复更改,直到所有线程均执行完毕。
package Thread;
/**
* 信号灯法
*/
public class CooperationTest02 {
public static void main(String[] args) {
HuaWeiLight huaWeiLight=new HuaWeiLight();
HuaWei huaWei=new HuaWei(huaWeiLight);
ShoppingPeople shop=new ShoppingPeople(huaWeiLight);
huaWei.start();
shop.start();
}
}
//生产者
class HuaWei extends Thread{
HuaWeiLight huaWeiLight;
public HuaWei(HuaWeiLight huaWeiLight){
this.huaWeiLight=huaWeiLight;
}
@Override
public void run() {
System.out.println("手机抢的也太快了吧,再来一波生产吧");
huaWeiLight.product();
System.out.println("p40又有货了,机不可失失不再来,快快抢购");
}
}
//消费者
class ShoppingPeople extends Thread{
HuaWeiLight huaWeiLight;
public ShoppingPeople(HuaWeiLight huaWeiLight){
this.huaWeiLight=huaWeiLight;
}
@Override
public void run() {
for(int i=0;i<40;i++){
HuaWeiP40 p40=huaWeiLight.sell();
}
}
}
//信号
//华为手机的某个机型是否还有库存供购买,如果没有了,消费者改变信号灯提醒华为生产者生产,此时信号灯为false,
//当华为生产了一定的手机数量后开始再次投入市场,改变信号灯,提醒顾客购买 此时信号灯为true
class HuaWeiLight{
HuaWeiP40[] p40s=new HuaWeiP40[40];
boolean flag=false;
int count=0;
//生产手机
public synchronized void product( ){
if(flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//假如每次生产了40台手机后才能投入市场销售
for(int i=1;i<=40;i++){
p40s[count]=new HuaWeiP40(i);
count++;
}
if(count==p40s.length){
this.flag=!this.flag;
this.notifyAll();
}
}
//销售手机
public synchronized HuaWeiP40 sell() {
if (!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count--;
HuaWeiP40 p40= p40s[count];
System.out.println("购买了第"+p40.id+"台");
if(count==0) {
this.flag = !this.flag;
System.out.println("又没抢到p40,华为快快生产吧");
this.notifyAll();
}
return p40;
}
}
class HuaWeiP40{
int id;
public HuaWeiP40(int id){
this.id=id;
}
}
package RelevantThread;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Timer;
import java.util.TimerTask;
public class TimerTest {
public static void main(String[] args) {
Timer timer=new Timer();
//timer.schedule(new MyTask(), 1000);//一秒后执行一次任务
//timer.schedule(new MyTask(), 1000,1000);//每一一秒执行一次
Calendar cal=new GregorianCalendar(2021,7,15,15,45);
timer.schedule(new MyTask(), cal.getTime(),1000);//从某一个具体的时间点开始每一秒执行一次任务
}
}
class MyTask extends TimerTask {
public void run(){
for(int i=0;i<10;i++){
System.out.println("hello JR");
}
System.out.println("-------end-------");
}
}