一、进程与线程
1.进程
进程:操作系统在运行的一个任务
进程中所包含的一个或多个线程,线程只能属于一个进程并且只能访问该进程所拥有的资源。
2.线程
线程:程序中一个顺序的单元执行流程,代码一句一句的执行知道最后一句执行完毕
3.线程与进程区别
一个进程至少有一个线程。
线程的划分尺度小于进程,使得多线程程序的并发性高。另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
线程在执行过程中与进程的区别在于每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用来实现进程的调度和管理以及资源分配。
4.线程使用场合
在一个程序中需要同时完成多个任务的情况,将每个任务定义成一个线程
5.并发原理
多个线程同时进行,os将时间划分为很多时间片段,尽可能分配给每一个线程,获取时间片段的线程呗CPU运行,而其他线程全部等待
6.线程的状态
1.创建(New)
2.就绪(Runnable)
①IO Block
②Sleep Block(阻塞状态)
③Wait Block(等待状态)
3.运行(Running)
4.死亡(Dead)
新建状态:
当使用new创建一个线程时,线程还没有开始运行,此时线程处于新建状态。当一个线程处于新建状态时,程序中的代码还没有开始运行。
就绪状态:
一个新建的线程并不能自动运行,要执行线程必须调用线程的start()方法。当线程对象调用start()方法即启动了线程,start()方法创建线程运行的系统资源,并调度线程运行run()方法。当start()方法返回后,线程就处于就绪状态。因为在单CPU的计算机系统中,不可能同时运行多个线程,一个时刻仅有一个线程处于运行状态。因此此时可能有多个线程处于就绪状态。对多个处于就绪状态的线程是由Java运行时系统的线程调度程序(thread scheduler)来调度的。
运行状态:
当线程获得CPU时间后,它才进入运行状态,真正开始执行run()方法。
阻塞状态:
1)线程通过调用sleep方法进入睡眠状态;
2)线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者;
3)线程试图得到一个锁,而该锁正被其他线程持有;
4)线程在等待某个触发条件;
线程提供了一个静态方法:static void sleep(long ms)
该方法可以让运行这个方法的线程进入阻塞状态指定毫秒
public class SleepDemo {
public static void main(String[] args) {
System.out.println("正在启动程序");
Scanner scan = new Scanner(System.in);
for (int time = scan.nextInt();time >0;time --){
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("时间到!");
}
System.out.println("程序结束");
}
}
sleep方法要求我们必须除了中断异常:InterruptedException
当一个线程调用sleep方法处于睡眠阻塞的过程中,该线程的Interrupt()方法被调用
那么就会中断该线程的睡眠阻塞,此时sleep方法会抛出中断异常
死亡状态:
有两个原因会导致线程死亡:
1> run方法正常退出而自然死亡,
2> 一个未捕获的异常终止了run方法而使线程猝死。
二、创建线程
1.使用Thread创建并启动线程
多线程:多个线程同时进行
线程的运行时并发的,实际cpu执行时是会让各线路中的代码走走停停,利用CPU超高的计算性能模拟他们同事运行的效果,这个现象称为并发
所以不是真实的同时运行
创建线程的方式有两种:
第一种是:继承Thread并重写run方法
创建线程的优点在于简单直接,使用匿名内部类创建时比较方便但是也存在两个明显的缺点
1:由于java是单继承的,这就导致如果继承了Thread就无法在继承其他类,这在实际开发中很不方便‘因为我们通常继承一个类是为了复用方法但当又要有线程的特性时就存在了继承冲突
2:直接重写run方法 相当于将线程要执行的任务定义在了线程中,这样不利于线程的复用。线程应当只是该变量程序执行的方式(并发执行),而不应当关心具体干什么。
public class ThreadDemo1 {
public static void main(String[] args) {
Thread t1 = new Thread1();
Thread t2 = new Thread2();
/*
启动线程时要调用线程的start方法,而不能直接调用run方法,否则不适并发运行的
start方法调用后线程会纳入到线程调度中统一管理,start方法调用后当该线程第一次获取时间片后
会自动调用run方法
*/
t1.start();
t2.start();
}
}
class Thread1 extends Thread{
public void run(){
for (int i = 0; i < 10000; i++) {
System.out.println("我是程序猿!");
}
}
}
class Thread2 extends Thread{
public void run(){
for (int i = 0; i < 10000; i++) {
System.out.println("我喜欢java编程");
}
}
}
class Thread3 extends Thread{
public void run(){
for (int i = 0; i < 10000; i++) {
System.out.println("我想要高薪资");
}
}
}
2.使用Runnable创建并启动线程
第二种创建线程的方式:实现runnable接口单独定义线程任务
public class ThreadDemo2 {
public static void main(String[] args) {
//1实例化任务
Runnable r1 = new Runnable1();
Runnable r2 = new Runnable2();
//2实例化线程
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
}
}
class Runnable1 implements Runnable{
public void run(){
for (int i = 0; i < 10000; i++) {
System.out.println("欢迎光临");
}
}
}
class Runnable2 implements Runnable{
public void run(){
for (int i = 0; i < 10000; i++) {
System.out.println("你好");
}
}
}
3.使用内部类启动线程
通常我们可以通过匿名内部类的方式创建线程,使用该方式可以简化编写代码的复杂度,当一个线程仅需要一个实例时我们通常使用这种方式来创建。
Thread thread = new Thread(){//匿名类方式创建线程
public void run(){
//线程体
}
};
thread.start();//启动线程
Runnable runnable = new Runnable(){//匿名类方式创建线程
public void run(){
}
};
Thread thread = new Thread(runnable);
thread.start();//启动线程
三、线程操作API
1.获取线程信息
Thread提供了一个静态方法:static Thread crrentThread()
该方法可以获取运行这个方法的线程
java中所有的代码都是靠线程运行的,main方法也不例外,该线程是虚拟机自动创建出来的,
用于开始执行main方法,因此也成为主线程。每条线程执行代码都是顺序执行,多个线程之间才是并发执行
Thread main = Thread.currentThread();
//获得线程的名字
String name = main.getName();
System.out.println("名字:"+name);
//获取线程的唯一标识
long id = main.getId();
System.out.println("id:"+id);
2.线程优先级
线程启动就纳入线程调度器中统一管理,调度器会尽可能均匀分配时间给每个线程,是的他们得以并发运行,线程是不能主动索取时间片的。通过调整线程的优先级可以最大程度大的改善线程获取cpu时间的频率
原则上优先级越高的线程获取CPU的时间片的次数越多
线程的优先级分为10个等级,用整数1-10表示,其中1是最低优先级,5是默认值,10位最高优先级
通过调用线程的setPriority方法设置优先级
//获取线程的优先级
int priority = main.getPriority();
System.out.println("线程的优先级:"+priority);
public class PriorityDemo {
public static void main(String[] args) {
Thread max = new Thread(){
public void run(){
for (int i = 0; i < 1000; i++) {
System.out.println("max");
}
}
};
Thread min = new Thread(){
public void run(){
for (int i = 0; i < 1000; i++) {
System.out.println("min");
}
}
};
Thread norm = new Thread(){
public void run(){
for (int i = 0; i < 1000; i++) {
System.out.println("nor");
}
}
};
max.setPriority(10);
min.setPriority(1);
max.start();
norm.start();
min.start();
}
}
3.守护线程
//是否为守护线程
boolean isDaemon = main.isDaemon();
守护线程也称为后台线程,他是调用线程的setDaemon(true)方法转变而来的,因此创建上与普通的线程没有区别,唯一的区别就是结束时机上有一点不同:进程的结束。当进程中的所有普通线程都结束时,进程就会结束,此时所有的守护线程会被强制杀死
通常守护线程上执行任务是不关心何时结束,但是程序主要的工作都干完后要跟着结束的任务就可以放在守护线程上执行。
比如:GC就是运行在守护线程上的任务。
public class DaemonThreadDemo {
public static void main(String[] args) {
Thread rose = new Thread(){
public void run(){
for (int i = 0; i < 5; i++) {
System.out.println("rose:let's me go!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("rose:啊啊啊啊啊AAAAAAAAAAaaaaaaaaa........");
System.out.println("噗通~");
}
};
Thread lily = new Thread(){
public void run (){
while(true){
System.out.println("lily:you jump! i am jump!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
rose.start();
lily.setDaemon(true);//必须在线程start之前设置
lily.start();
// System.out.println("主线程死了");
// while(true);
}
}
4.是否活着
线程是否活着
boolean isAlive = main.isAlive();
5.是否被中断
//是否被中断
boolean isInterrupted = main.isInterrupted();
6.Sleep方法
线程提供了一个静态方法:static void sleep(long ms)该方法可以让运行这个方法的线程进入阻塞状态指定毫秒
public class SleepDemo {
public static void main(String[] args) {
System.out.println("正在启动程序");
Scanner scan = new Scanner(System.in);
for (int time = scan.nextInt();time >0;time --){
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("时间到!");
}
System.out.println("程序结束");
}
}
sleep方法要求我们必须除了中断异常:InterruptedException
当一个线程调用sleep方法处于睡眠阻塞的过程中,该线程的Interrupt()方法被调用,那么就会中断该线程的睡眠阻塞,此时sleep方法会抛出中断异常
public class SleepDemo2 {
public static void main(String[] args) {
Thread lin = new Thread(){
public void run(){
try {
System.out.println("林:刚美完容,睡一会吧!");
System.out.println("林:醒了");
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("林:干嘛呢!干嘛呢!干嘛呢!");
}
};
Thread huang = new Thread(){
public void run(){
for (int i = 0; i < 5; i++) {
System.out.println("黄:80!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("哐当!");
System.out.println("黄:搞定!");
lin.interrupted();
}
};
lin.start();
huang.start();
}
}
7.Yield方法
8.Join方法
线程提供的方法:void join()
该方法可以提供协调线程的同步运行,其允许一个线程在另一个线程上等待,直到该线程运行完毕之后再继续运行。
同步运行:指多个线程运行方式是有先后顺序的进行
异步运行:指多个线程各干各的,线程本身就是异步运行的。
public class JoinDemo {
public static boolean isFinish = false;//表示图片是否下载完
public static void main(String[] args) {
/*
java语要求:当一个方法的局部内部类中引用了这个方法的其他局部变量时,这个变量必须被声明为final的,
JDK8之后可以不显示的用Final关键字修饰这个变量,但是依然有这个要求
*/
// boolean isFinish = false;
Thread download = new Thread(){
public void run(){
System.out.println("图片开始下载...");
for (int i = 0; i <= 100; i++) {
System.out.println("down"+i+"%");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
}
}
System.out.println("down:图片下载完毕!");
isFinish = true;
}
};
Thread show = new Thread(){
public void run(){
try {
System.out.println("开始显示文字...");
Thread.sleep(2000);
System.out.println("show:文字显示完毕!");
System.out.println("show:等待下载图片...");
download.join();//show线程会进入阻塞状态,知道download执行完毕才会接触
System.out.println("show:开始显示图片...");
if (!isFinish){
throw new RuntimeException("图片加载失败!");
}
System.out.println("show:图片显示完毕!");
}catch (InterruptedException e){
e.printStackTrace();
}
}
};
download.start();
show.start();
}
}
四、线程同步
1.synchronized关键字
多线程并发安全问题
当多个线程并发操作同一临界资源,由于线程切换时机不确定,导致多个线程执行操作的顺序出现混乱。
严重时可能导致系统瘫痪
临界资源:对该资源的完整操作同一时间只能有一个线程完成。
public class SyncDEmo {
public static void main(String[] args) {
Table table = new Table();
Thread t1 = new Thread() {
public void run() {
while (true) {
int bean = table.getBean();
Thread.yield();//让运行该方法的线程主动让出CPU时间
System.out.println(getName() + ":" + bean);
}
}
};
Thread t2 = new Thread() {
public void run() {
while (true) {
int bean = table.getBean();
Thread.yield();//让运行该方法的线程主动让出CPU时间
System.out.println(getName() + ":" + bean);
}
}
};
t1.start();
t2.start();
}
}
class Table{
private int beans = 20;
/*
* 当一个方法使用synchronized修饰后,这个方法称为同步方法,即:多个线程执行时,同一时刻只能有一个线程在方法內部执行
* 将异步操作改为同步操作可以有效解决并发安全问题
* @return
*/
public synchronized int getBean(){
if (beans==0){
throw new RuntimeException("不爱你了");
}
Thread.yield();//让运行该方法的线程主动让出CPU时间
return beans--;
}
}
2.锁机制
有效的缩小同步范围可以在保证并发安全问题的前提下尽可能提高并发率
同步块可以更精准的控制需要多个线程同步运行的代码片段。使用更灵活。
语法:
synchronized(同步监视器对象){
需要同步执行的代码片段
}
public class SyncDemo2 {
public static void main(String[] args) {
Shop shop = new Shop();
Thread t1 = new Thread(){
public void run(){
shop.buy();
}
};
Thread t2 = new Thread(){
public void run(){
shop.buy();
}
};
t1.start();
t2.start();
class Shop{
//在方法上使用synchronized,name同步监视器对象(上锁的对象)就是this
// public synchronized void buy(){
public void buy(){
try {
Thread t = Thread.currentThread();
System.out.println(t.getName() + "正在挑衣服...");
Thread.sleep(5000);
// synchronized(t) {
synchronized(this) {
System.out.println(t.getName() + "正在试衣服...");
Thread.sleep(5000);
}
System.out.println(t.getName() + "结账离开...");
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
3.选择合适的锁对象
静态方法上使用synchronize修饰后,这个方法一定具有同步效果
静态方法上使用是指定的同步监视器对象为当前类的类对象,一个class的实例,类对象的知识会在后面反射API中学习
JVM中每个被加载的类都具有一个class的实例与之对应,静态方法就是所这个对象,如果在静态方法中使用同步块是,通常也是指定这个对象。获取一个类的类对象可以用:类名.class
public class SyncDemo3 {
public static void main(String[] args) {
Thread t1 = new Thread(){
public void run(){
Boo.dosome();
}
};
Thread t2 = new Thread(){
public void run(){
Boo.dosome();
}
};
t1.start();
t2.start();
}
}
class Boo{
// public synchronized static void dosome(){
public static void dosome(){
synchronized(Boo.class){
try{
Thread t = new Thread();
System.out.println(t.getName()+"正在执行dosome方法");
Thread.sleep(5000);
System.out.println(t.getName()+"执行dosome方法完成");
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}
}
4.选择合适的锁范围
互斥锁
当使用synchronized锁定多个代码片段,并且指定的同步监视器对象是同一个时,这些代码片段之间就是互斥的
public class SyncDemo4 {
public static void main(String[] args) {
Aoo aoo = new Aoo();
Thread t1 = new Thread(){
public void run(){
aoo.methodA();
}
};
Thread t2 = new Thread(){
public void run(){
aoo.methodB();
}
};
t1.start();
t2.start();
}
}
class Aoo{
public synchronized void methodA(){
try{
Thread t = Thread.currentThread();
System.out.println(t.getName()+"正在运行A方法...");
Thread.sleep(5000);
System.out.println(t.getName()+"正在执行A方法...");
}catch(InterruptedException e){
e.printStackTrace();
}
}
public synchronized void methodB(){
try{
Thread t = Thread.currentThread();
System.out.println(t.getName()+"正在运行B方法...");
Thread.sleep(5000);
System.out.println(t.getName()+"正在执行B方法...");
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
5.静态方法锁
死锁
死锁指的是线程获得一个锁,并索要对方的锁,如果这个过程中两个线程都持有
自己的锁并索要对方的锁时,就形成了僵持的情况,这就是思索的产生。
public class DeathLockDemo {
public static void main(String[] args) {
Object chopsticks = new Object();
Object spoon = new Object();
Thread bei = new Thread(){
public void run(){
System.out.println("北方人拿起了筷子");
System.out.println("北方人拿起了筷子");
synchronized (chopsticks){
System.out.println("北方人开始用筷子吃饭");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
System.out.println("北方人吃完了饭,去拿勺");
synchronized (spoon){
System.out.println("北方人开始用勺喝汤");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("北方人喝完了汤");
}
System.out.println("北方人放下了勺");
}
System.out.println("北方人放下了筷子");
}
};
Thread nan = new Thread(){
public void run(){
System.out.println("南方人拿起了勺子");
synchronized (spoon){
System.out.println("南方人开始用勺子喝汤");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
System.out.println("南方人喝完了汤,去拿筷子");
synchronized (chopsticks){
System.out.println("南方人开始用筷子吃饭");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("南方人喝完了汤");
}
System.out.println("南方人放下了筷子");
}
System.out.println("南方人放下了勺子");
}
};
bei.start();
nan.start();
}
}