多线程
Process(进程)和 Thread(线程 )
进程是程序执行的一次执行过程,是一个动态的概念。是系统资源分配的一个单位。
通常一个进程可以包括多个线程。一个进程至少有一个线程,不然没有存在的意义。线程是CPU调度和执行的单位。
注意:
很多线程都是模拟出来的,真正的多线程指有多个cpu,即多核,如服务器,如果是模拟的多线程,指在一个CPU下在同一个时间节点,cpu只能执行一个代码,应为切换的很快,造成有同时执行的错觉。
线程状态转换图:
1.创建线程 new-----创建状态
2.通过start()方法起动线程-----进入就绪状态
3.在cpu的调度下------进入运行状态
4.当调用sleep,或同步锁定进入阻塞状态,代码不往下执行,解除阻塞状态,又回归运行状态
5.线程中断或结束—进入死亡状态
核心概念
1.线程就是独立的执行程序
2.在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程
3. main()称之为主线程,为系统的入口,用于执行整个程序;
4.在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与 操作系统紧密相关的,先后顺序是不能认为的干预的。
5.对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制;
6.线程会带来额外的开销,如cpu调度时间,并发控制开销。
7.每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
多线程的创建
三种创建方法
1.继承Thread类
2.实现Runnable接口
3.实现Callable接口
实现Runnable接口(由于java的单继承,推荐使用此方法)
1.自定义类实现runnable接口
2.实现run()方法,编写线程体
3.创建线程对象,通过Thread线程对象调用本线程,通过调用start()方法启动线程;(代理)
public class TestThread02 implements Runnable{
@Override
public void run() {
//run方法体
for (int i = 0; i < 20; i++) {
System.out.println("我在看代码---"+i);
}
}
public static void main(String[] args) {
//创建runnable接口实现类对象
TestThread02 t1=new TestThread02();
//创建线程对象,通过线程对象启动我们的线程:(代理)
/* Thread thread = new Thread(t1);
thread.start(); 可简化成*/
new Thread(t1).start();
for (int i = 0; i <200 ; i++) {
System.out.println("多线程学习---"+i);
}
}
}
执行结果(部分):从中可看出多线程交替执行
线程类创建步骤(继承Thread类)
(Thread实现了runnable接口)
1.自定义线程类继承Thread类
2.重写线程类的run()方法
3.创建线程对象,调用start()方法启动线程;
public class TestThread extends Thread{
private String name;
private String url;
public TestThread(String name,String url) {
this.name=name;
this.url=url;
}
//线程执行体
@Override
public void run() {
System.out.println("线程执行了"+name+" "+url);
}
public static void main(String[] args) {
TestThread t1=new TestThread("111", "1111");
TestThread t2=new TestThread("222", "2222");
TestThread t3=new TestThread("333", "3333");
t1.start();
t2.start();
t3.start();
}
}
执行结果:
线程每次执行顺序不一致,看cpu分配情况
例如:龟兔赛跑
public class Race implements Runnable {
private static String winner;
//定义静态变量 获胜者
@Override
public void run() {
for (int i = 0; i <=100 ; i++) {//100米跑道
if (gameover(i)==true){
break;//判断是否有获胜者 有则结束循环
}
//模拟兔子睡觉 每10米睡1ms
if (Thread.currentThread().getName().equals("兔子")&&i%10==0){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"跑了"+i+"米");
}
}
//判断比赛是否结束
public boolean gameover(int dept){
if (winner!=null){
return true;
}else if (dept>=100){
winner=Thread.currentThread().getName();
System.out.println(winner+"获胜");
return true;
}
return false;
}
public static void main(String[] args) {
Race race = new Race();
new Thread(race,"兔子").start();
new Thread(race,"乌龟").start();
}
}
通过实现Callable接口实现多线程(了解)
1.实现callable接口,注意需要返回值类型
2.重写call方法(相当于重写run方法)
3.创建目标对象
4.创建执行服务 ExecutorService service= Executors.newFixedThreadPool(1);
5.提交执行 Future r1=service.submit(callableDemo); 注意Boolean与接口返回值类型一致
6.获取结果 Boolean rs1=r1.get() 注意接受的结果类型与接口返回值类型一致
7. 关闭服务 service.shutdownNow();
public class CallableDemo implements Callable<Boolean> {
//实现callable接口,注意需要返回值类型
//重写call方法
@Override
public Boolean call() throws Exception {
System.out.println("多线程");
return true;
}
public static void main(String[] args) throws Exception {
//创建目标对象
CallableDemo callableDemo = new CallableDemo();
//创建执行服务
ExecutorService service= Executors.newFixedThreadPool(1);//有几个线程 ,数字就是几
//提交执行
Future<Boolean> r1=service.submit(callableDemo);
//获取结果
Boolean rs1=r1.get();//注意异常
//关闭服务
service.shutdownNow();
}
}
线程的并发问题
当多个线程操作同一个对象时,线程不安全,可能会出现同一个结果被多个线程调用,数据紊乱。
//多个线程同时操作同一个对象(并发)
//买车票
public class Ticket implements Runnable {
private int ticket=10;
@Override
public void run() {
//hread.currentThread().getName()-----得到线程名
while (true) {
if (ticket<=0){
break;
}
//模拟延时
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "买到了第" + (ticket--)+"张票");
}
}
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(ticket,"小米").start();
new Thread(ticket,"小兰").start();
new Thread(ticket,"黄牛").start();
}
}
运行结果:如图出现了三张7号票出现了0,-1票,在现实中不可能出现,线程不安全,数据紊乱
静态代理
注意与Thread对比
代理:不改变实现类的情况下,对实现类进行功能的增加
静态 所谓的静态就是在创建代理类的时候,接口和被代理类都已经被固定了,无法改变,代理类就只能这一种类
1.真实对象和代理对象实现同一个接口
2.代理对象要代理真实角色
静态代理的步骤
1.定义共同的主题接口(以找工作为例)
interface Find_work {
void findwork();
}
2。定义该接口的实现类,被代理对象
class Person implements Find_work{
@Override
public void findwork() {
System.out.println("投简历");
}
}
3.创建上述实现类的代理类(两个类需要实现共同的主题接口,这样才能对原有功能进行增强)
class share implements Find_work{
private Find_work find_work;
public share(Find_work find_work) {
this.find_work = find_work;
}
//需要传入被代理的对象,才可以new
@Override
public void findwork() {
System.out.println("before。。。");
System.out.println("投简历");
System.out.println("HR审查");
System.out.println("面试通知");
System.out.println("after。。。");
//进行功能增强
}
}
测试及返回结果
@Test
public void test(){
Find_work find_work=new Person();//多态,接口new实现类
share share = new share(find_work);
share.findwork();
}
线程停止
1.不推荐使用jdk使用的stop()destory()方法,已过时
2.推荐使用标识符,通过转换标识符来停止线程
public class ThreadStop implements Runnable{
//通过标识符控制停止
private boolean flag=true;
@Override
public void run() {
int i=0;
while (flag){
System.out.println("Thread----run :" + i++);
}}
//设计一个公开方法。定义标识符反转方法---用以停止线程
public void stop() {
this.flag=false;
}
public static void main(String[] args) {
ThreadStop threadStop=new ThreadStop();
new Thread(threadStop).start();
//相当于一个计时器---当此i=900时,执行公共方法,停止线程
for (int i = 0; i < 1000; i++) {
System.out.println("main"+i);
if (i==900){
threadStop.stop();
System.out.println("Thread......stop");
}
}
}
}
部分运行截图
线程休眠 sleep
- sleep(数字) 指当前线程阻塞毫秒数
- sleep可用于模拟网络延时,倒计时等,时间结束后线程进入就绪状态
- 每个对象都有一个锁,sleep不会释放锁。
- 模拟网络延时,可以放大线程安全问题,便于检测
- 倒计时代码如下
//模拟倒计时
public class TestSleep {
public static void main(String[] args) {
//模拟时间倒计时
Date time = new Date(System.currentTimeMillis());//获取系统时间
while (true){
try {
Thread.sleep(1000);
System.out.println(new SimpleDateFormat("HH:mm:ss").format(time));
time = new Date(System.currentTimeMillis());//更新时间
} catch (Exception e) {
e.printStackTrace();
}
}
}
//模拟倒计时方法时调用此方法
public static void timedown() throws InterruptedException {
int num=0;
while (true) {
Thread.sleep(1000);
System.out.println(num--);
if (num==0){
break;
}
}
}
}
线程礼让 yield()
将 正在运行的线程停止下来,不阻塞,将其从运行状态转化为就绪状态,让cpu重新调度(注意礼让不一定成功,看cpu调度)
强制执行 join()
join合并线程,待此线程执行完毕后,再执行其他线程,其他线程阻塞(可以看成插队)
线程状态观测
线程状态
- NEW 尚未启动的线程状态
- RUNNABLE (runnable)运行中的线程处于此状态
- BLOCKED (blocked)被阻塞等待监视器锁定的线程状态
- WAITING 等待另一个线程执行特定动作的线程处于此状态
- TIMED_WAITING 等待另一个线程执行动作到达指定等待时间的线程处于此状态
- TERMINATED(terminated)
public class TestStatic {
public static void main(String[] args) {
Thread thread = new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);//休息一秒,使我们更容易观察异常
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("--------------------");//分割
});
//观察状态----New
Thread.State state = thread.getState();
System.out.println(state);
//runnable
thread.start();
state= thread.getState();
System.out.println(state);
//
while (thread.getState()!=Thread.State.TERMINATED)//只要线程不停止,一直输出状态
{
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
state= thread.getState();
System.out.println(state);
}
}
}
执行结果:
线程优先级
java提供一个线程调度器来监控程序中启动后 进入就绪状态的所有线程,调度器按优先级决定该调度那个线程执行。
线程优先级用数字表示范围从1-10;
Thread.MIN_PRIORITY=1;
Thread.MAX_PRIORITY =10;
Thread.NORM_PRIORITY=5;
改变或获取优先级方法
getPriority.setPriority(int XX)
优先级一般在start之前
public class TestPriority {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+"----"+Thread.currentThread().getPriority());
testthread t = new testthread();
Thread t1=new Thread(t,"1");
Thread t2=new Thread(t,"2");
Thread t3=new Thread(t,"3");
Thread t4=new Thread(t,"4");
t1.setPriority(5);
t2.setPriority(7);
t3.setPriority(6);
t4.setPriority(9);
t1.start();
t2.start();
t3.start();
t4.start();
}
public static class testthread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"----"+Thread.currentThread().getPriority());
}
}
}
执行结果:
优先级高的不一定先执行,但大多数时候还是优先级高的先执行
守护(daemon)线程
- 线程分为守护线程和用户线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕
- 守护线程可以:用户后台记录操作日志,监控内存,垃圾回收等
方法
setDaemon(true);//默认为fales 表示用户线程,正常线程都是用户线程,为true才为守护线程
线程同步机制
并发:同一个对象被多个线程同步执行;
处理多线程问题时,多个线程访问同一个对象,并且有些线程还要修改这个对象,此时我们就需要线程同步,线程同步相当于一个等待机制(线程排队访问对象),多个需要同时访问此对象的线程,进入这个对象的等待池形成队列,等前一个对象执行完毕,下一个对象再使用。
线程同步需要队列加锁
同步方法及同步块
由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问 冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入*锁机制 synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待, 使用后释放锁即可.
存在以下问题;
- 一个线程持有锁会导致其他所有需要此锁的线程挂起;
- 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引 起性能问题;
- 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒 置,引起性能问题。
同步方法:public synchronized void method(int args){}
~~~~
ynchronized方法控制对“对象”的访问,每个对象对应一把锁),每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞, 方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获 得这个锁,继续执行
注意:若将一个大方法声明为synchronized 会影响效率
同步块:synchronized (Obj){}
Obj称之为同步监视器
Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是 这个对象本身,或者是class
同步监视器的执行过程
- 第一个线程访问,锁定同步监视器,执行其中代码,
- 第二个线程访问,发现同步监视器被锁定,无法访问,
- 第一个线程访问完毕,解锁同步监视器
- 第二个线程访问,发现同步监视器没有锁,然后锁定并访问
死锁
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而 导致两个或者多个线程都在等待对方释放资源,都停止执行的情形.某一个同步块 同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题,
//死锁:多个线程互相锁着对方所需的资源,形成僵持。
public class DeadRocket {
public static void main(String[] args) {
eat eat1= new eat("1", 0);
eat eat2= new eat("2", 2);
eat1.start();
eat2.start();
}
static class apple{}
static class bannner{}
public static class eat extends Thread{
//需要的资源只有一份用static保证
static apple apple = new apple();
static bannner banner = new bannner();
int choice;
String name;
public eat(String name, int choice) {
this.choice = choice;
this.name = name;
}
@Override
public void run() {
try {
eatrun();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void eatrun() throws InterruptedException {
if (choice==0){
synchronized (apple){//获得苹果的锁
System.out.println(this.getName()+"apple");
Thread.sleep(1000);//可放大错误
synchronized (banner){//一秒后获得banner的锁
System.out.println(this.getName()+"banner");
Thread.sleep(1000);
}
}
}else{
synchronized (banner){//获得banner的锁
System.out.println(this.getName()+"banner");
Thread.sleep(2000);
synchronized (apple){//两秒后获得苹果的锁
System.out.println(this.getName()+"apple");
Thread.sleep(2000);
}
}
}
}
}
}
可知1 2 线程都卡住了,所需资源都被对方锁住,无法执行,发生死锁,
可将1中在apple锁中的banner锁放出来,与apple锁平行,2同理,可解除死锁
产生死锁的四个必要条件:
- 互斥条件:一个资源每次只能被一个进程使用。
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件;进程已获得的资源,在末使用完之前,不能强行剥夺。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
上面列出了死锁的四个必要条件, 我们只要想办法破其中的任意一个或多个条件 就可以避免死锁发生
Lock(锁)
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//定义lock锁
public class LockTest {
public static void main(String[] args) {
ticket t = new ticket();
new Thread(t).start();
}
}
class ticket implements Runnable{
int num=100;
//定义lock 锁
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
lock.lock();
try {
if (num>0){
System.out.println(num--);
}else {
break;
}
}finally {
lock.unlock();//解锁
}
}
}
}
经典问题:生产者消费者问题
引用方法
方法一 管道法
方法二 信号灯法
线程池Pool
public class PoolTest{
public static void main(String[] args) {
//创建线程服务创建线程池,括号内的参数是线程池大小
ExecutorService service = Executors.newFixedThreadPool(10);
//执行
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());
}
}