进程、线程、多线程的概念
进程:正在进行中的程序。
线程:进程中一个负责程序执行的控制单元。
P.S.
1、一个进程中可以有多个执行路径,称之为多线程。
2、一个进程中至少要有一个线程。
3、开启多个线程是为了同时运行多部分代码,每一个线程都有自己运行的内容,这个内容可以称为线程要执行的任务。
多线程的好处:解决了多部分代码同时运行的问题。
多线程的弊端:线程太多,会导致效率的降低。
其实,多个应用程序同时执行都是CPU在做着快速的切换完成的。这个切换是随机的。CPU的切换是需要花费时间的,从而导致了效率的降低。
JVM启动时启动了多条线程,至少有两个线程可以分析的出来:
1.执行main函数的线程,该线程的任务代码都定义在main函数中。
2.负责垃圾回收的线程。
创建线程方式一:继承Thread类
1.定义一个类继承Thread类。
2.覆盖Thread类中的run方法。
3.直接创建Thread的子类对象创建线程。
4.调用start方法开启线程并调用线程的任务run方法执行。
jvm创建的主线程的任务都定义在了主函数中。而自定义的线程,它的任务在哪儿呢?
Thread类用于描述线程,线程是需要任务的。所以Thread类也有对任务的描述。这个任务就是通过Thread类
中的run方法来体现。也就是说,run方法就是封装自定义线程运行任务的函数,run方法中定义的就是线程要运行的任务代码。
开启线程是为了运行指定代码,所以只有继承Thread类,并复写run方法,将运行的代码定义在run方法中即可。
class Demo extends Thread
{
private String name;
Demo(String name)
{
this.name = name;
}
public void run()
{
for(int x = 0;x<10;x++)
{
System.out.println(name+"...x="+x+"+...ThreadName="+Thread.currentThread().getName());
}
}
}
class ThreadDemo
{
public static void main(String[] args)
{
Demo d1 = new Demo("旺财");
Demo d2 = new Demo("xiaoqiang");
d1.start();
d2.start();
for(int x = 0;x<10;x++)
{
System.out.println("...x="+x+"+...over="+Thread.currentThread().getName());
}
}
}P.S.
1、可以通过Thread的getName方法获取线程的名称,名称格式:Thread-编号(从0开始)。
2、Thread在创建的时候,该Thread就已经命名了。源码如下:
创建线程方式二:实现Runnable接口
1.定义类实现Runnable接口。
2.覆盖接口中的run方法,将线程的任务代码封装到run方法中。
3.通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递。为什么?因为线程的任务都封装在Runnable接口子类对象的run方法中。所以要在线程对象创建时就必须明确要运行的任务。
4.调用线程对象的start方法开启线程。
实现Runnable接口的好处:
1.将线程的任务从线程的子类中分离出来,进行了单独的封装,按照面向对象的思想将任务封装成对象。
2.避免了Java单继承的局限性。所以,创建线程的第二种方式较为常用。
class Demo implements Runnable
{
public void run()
{
show();
}
public void show()
{
for(int x=0;x<20;x++)
{
System.out.println(Thread.currentThread().getName()+"..."+x);
}
}
}
class ThreadDemo
{
public static void main(String[] args)
{
Demo d = new Demo();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
t2.start();
}
}Thread类、Runnable接口内部源码关系模拟代码:
class Thread
{
private Runnable r;
public Thread(){}
public Thread(Runnable r)
{
this.r = r;
}
public void run()
{
if(r!=null)
r.run();
}
public void start()
{
run();
}
}
class ThreadImp1 implements Runnable
{
public void run()
{
System.out.println("runnable run");
}
}
class ThreadDemo
{
public static void main(String[] args)
{
ThreadImp1 ti = new ThreadImp1();
Thread t = new Thread(ti);
t.start();
}
}
class SubThread extends Thread
{
public void run()
{
System.out.println("haha");
}
}
class ThreadDemo5
{
public static void main(String[] args)
{
SubThread s = new SubThread();
s.start();
}
<span style="background-color: rgb(255, 255, 255);">}</span>线程安全问题
线程安全问题产生的原因:
1.多个线程在操作共享的数据。
2.操作共享数据的线程代码有多条。
当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生。
线程安全问题的解决方案
思路:
就是将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程不可以参与运算。必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。
在java中,用同步代码块就可以解决这个问题。
同步代码块的格式:
synchronized(对象){
需要被同步的代码;
}
同步的好处:解决了线程的安全问题。
同步的弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
同步的前提:必须有多个线程并使用同一个锁。
class Ticket implements Runnable
{
private int num = 1000;
Object obj = new Object();
public void run()
{
while(true)
{
if(num>0)
{
synchronized(obj)
{
if(num>0)
{
System.out.println(Thread.currentThread().getName()+"...sale..."+num--);
}
else
break;
}
}
else
break;
}
}
}
class TicketDemo
{
public static void main(String[] args)
{
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}原因分析:上图显示安全问题已被解决,原因在于Object对象相当于是一把锁,只有抢到锁的线程,才能进入同步代码块向下执行。因此,当num=1时,CPU切换到某个线程后,如上图的Thread-3线程,其他线程将无法通过同步代码块继而进行if判断语句,只有等到Thread-3线程执行完“num--”操作(此后num的值为0),并且跳出同步代码块后,才能抢到锁。其他线程即使抢到锁,然而,此时num值已为0,也就无法通过if语句判断,从而无法再执行“num--”的操作了,也就不会出现0、-1、-2等情况了。
利用同步代码块解决安全问题案例:
需求:储户,两个,每个都到银行存钱,每次存100,共存三次。
class Bank
{
private int sum;
public void add(int num)
{
synchronized(this)//同步代码块 //同步代码块的锁是任意对象
{
sum = sum+num;
System.out.println("sum="+sum);
}
}
/*public synchronized void add(int num)//同步函数 //同步函数的锁是固定的this
{
sum = sum+num;
System.out.println("sum="+sum);
}*/
}
class Cus implements Runnable
{
private Bank b = new Bank();
public void run()
{
for(int x= 0;x<3;x++)
{
b.add(100);
}
}
}
class BankDemo
{
public static void main(String[] args)
{
Cus c = new Cus();
Thread t1 = new Thread(c);
Thread t2 = new Thread(c);
t1.start();
t2.start();
}
}死锁常见情景之一:同步的嵌套。
/*
死锁:常见情景之一:同步的嵌套。
*/
class Ticket implements Runnable
{
private int num = 100;
Object obj = new Object();
boolean flag = true;
public void run()
{
if(flag)
while(true)
{
synchronized(obj)
{
show();
}
}
else
while(true)
this.show();
}
public synchronized void show()
{
synchronized(obj)
{
if(num>0)
{
try{Thread.sleep(10);}catch (InterruptedException e){}
System.out.println(Thread.currentThread().getName()+".....sale...."+num--);
}
}
}
}
class DeadLockDemo
{
public static void main(String[] args)
{
Ticket t = new Ticket();
// System.out.println("t:"+t);
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
try{Thread.sleep(10);}catch(InterruptedException e){}
t.flag = false;
t2.start();
}
}
死锁示例2:class Test implements Runnable
{
private boolean flag;
Test(boolean flag)
{
this.flag = flag;
}
public void run()
{
if(flag)
{
while(true)
synchronized(MyLock.locka)
{
System.out.println(Thread.currentThread().getName()+"..if locka....");
synchronized(MyLock.lockb) {
System.out.println(Thread.currentThread().getName()+"..if lockb....");
}
}
}
else
{
while(true)
synchronized(MyLock.lockb)
{
System.out.println(Thread.currentThread().getName()+"..else lockb....");
synchronized(MyLock.locka)
{
System.out.println(Thread.currentThread().getName()+"..else locka....");
}
}
}
}
}
class MyLock
{
public static final Object locka = new Object();
public static final Object lockb = new Object();
}
class DeadLockTest
{
public static void main(String[] args)
{
Test a = new Test(true);
Test b = new Test(false);
Thread t1 = new Thread(a);
Thread t2 = new Thread(b);
t1.start();
t2.start();
}
}
线程间通信
线程间通信涉及的方法
多个线程在处理统一资源,但是任务却不同,这时候就需要线程间通信。
等待/唤醒机制涉及的方法:
1. wait():让线程处于冻结状态,被wait的线程会被存储到线程池中。
2. notify():唤醒线程池中的一个线程(任何一个都有可能)。
3. notifyAll():唤醒线程池中的所有线程。
P.S.
1、这些方法都必须定义在同步中,因为这些方法是用于操作线程状态的方法。
2、必须要明确到底操作的是哪个锁上的线程!
3、wait和sleep区别?
1)wait可以指定时间也可以不指定。sleep必须指定时间。
2)在同步中时,对CPU的执行权和锁的处理不同。
wait:释放执行权,释放锁。
sleep:释放执行权,不释放锁。
为什么操作线程的方法wait、notify、notifyAll定义在了object类中,因为这些方法是监视器的方法,监视器其实就是锁。
锁可以是任意的对象,任意的对象调用的方式一定在object类中。
生产者-消费者问题:
class Resource
{
private String name;
private String sex;
private boolean flag = false;
public synchronized void set(String name,String sex)
{
if(flag)
{
try
{
this.wait();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
this.name = name;
this.sex = sex;
flag = true;
this.notify();
}
public synchronized void out()
{
if(!flag)
{
try
{
this.wait();
}
catch(InterruptedException e)
{
e.printStackTrace();
}
}
System.out.println(name+"..."+sex);
flag = false;
this.notify();
}
}
class Input implements Runnable
{
Resource r;
Input(Resource r)
{
this.r = r;
}
public void run()
{
int x = 0;
while(true)
{
if(x==0)
{
r.set("mike","男");
}
else
{
r.set("lili","女");
}
x=(x+1)%2;
}
}
}
class Output implements Runnable
{
Resource r;
Output(Resource r)
{
this.r = r;
}
public void run()
{
while(true)
{
r.out();
}
}
}
class ResourceDemo
{
public static void main(String[] args)
{
Resource r = new Resource();
Input in = new Input(r);
Output out = new Output(r);
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
t1.start();
t2.start();
}
}
多生产-多消费者问题:
class Resource
{
private String name;
private int count = 1;
private boolean flag = false;
public synchronized void set(String name)
{
while(flag)
{
try
{
this.wait();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
this.name = name+count;
count++;
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
flag = true;
this.notifyAll();
}
public synchronized void out()
{
while(!flag)
{
try
{
this.wait();
}
catch(InterruptedException e)
{
e.printStackTrace();
}
}
flag = false;
this.notifyAll();
System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);
}
}
class Producer implements Runnable
{
private Resource r;
Producer(Resource r)
{
this.r = r;
}
public void run()
{
int x = 0;
while(true)
{
r.set("烤鸭");
}
}
}
class Consumer implements Runnable
{
private Resource r;
Consumer(Resource r)
{
this.r = r;
}
public void run()
{
while(true)
{
r.out();
}
}
}
class ProducerConsumerDemo
{
public static void main(String[] args)
{
Resource r = new Resource();
Consumer con = new Consumer(r);
Producer pro = new Producer(r);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(pro);
Thread t3 = new Thread(con);
Thread t4 = new Thread(con);
t1.start();
t2.start();
t3.start();
t4.start();
}
}JDK1.5新特性
同步代码块就是对于锁的操作是隐式的。
JDK1.5以后将同步和锁封装成了对象,并将操作锁的隐式方式定义到了该对象中,将隐式动作变成了显示动作。
Lock接口:出现替代了同步代码块或者同步函数,将同步的隐式操作变成显示锁操作。同时更为灵活,可以一个锁上加上多组监视器。
lock():获取锁。
unlock():释放锁,为了防止异常出现,导致锁无法被关闭,所以锁的关闭动作要放在finally中。
Condition接口:出现替代了Object中的wait、notify、notifyAll方法。将这些监视器方法单独进行了封装,变成Condition监视器对象,可以任意锁进行组合。
Condition接口中的await方法对应于Object中的wait方法。
Condition接口中的signal方法对应于Object中的notify方法。
Condition接口中的signalAll方法对应于Object中的notifyAll方法。
使用一个Lock、两个Condition修改上面的多生产者-多消费者问题。
import java.util.concurrent.locks.*;
class Resource
{
private String name;
private int count= 1;
private boolean flag = false;
//创建一个锁对象
Lock lock = new ReentrantLock();
//通过已有的锁获取锁上的监视器对象
Condition producer_con = lock.newCondition();
Condition consumer_con = lock.newCondition();
public void set(String name)
{
lock.lock();
try
{
while(flag)
{
try
{
producer_con.await();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
this.name = name+count;
count++;
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
flag = true;
consumer_con.signal();
}
finally
{
lock.unlock();
}
}
public void out()
{
lock.lock();
try
{
while(!flag)
{
try
{
consumer_con.await();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
flag = false;
producer_con.signal();
System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);
}
finally
{
lock.unlock();
}
}
}
class Producer implements Runnable
{
private Resource r;
Producer(Resource r)
{
this.r = r;
}
public void run()
{
while(true)
{
r.set("烤鸭");
}
}
}
class Consumer implements Runnable
{
private Resource r;
Consumer(Resource r)
{
this.r = r;
}
public void run()
{
while(true)
{
r.out();
}
}
}
class ProducerConsumerDemo
{
public static void main(String[] args)
{
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(pro);
Thread t3 = new Thread(con);
Thread t4 = new Thread(con);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
停止线程
class StopThreadDemo
{
public static void main(String[] args)
{
StopThread st = new StopThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.start();
t2.start();
int num = 1;
for(;;)
{
if(++num == 50)
{
t1.interrupt();//停止线程 会是线程run方法抛出InterruptedException异常
t2.interrupt();
break;
}
System.out.println("main..."+num);
}
System.out.println( "over");
}
}
class StopThread implements Runnable
{
private boolean flag = true;
public synchronized void run()
{
while(flag)
{
try
{
wait();
}
catch (InterruptedException e)
{
System.out.println(Thread.currentThread().getName()+"..."+e);
flag = false;
}
System.out.println(Thread.currentThread().getName()+"....");
}
}
public void setFlag()
{
flag = false;
}
}线程类的其他方法
setDaemon()方法:
1. 守护线程就是运行在系统后台的线程,如果JVM中只有守护线程,则JVM退出。2. Main主线程结束了(Non-daemon thread),如果此时正在运行的其他threads是daemon threads,JVM会使得这个threads停止,JVM也停下.如果此时正在运行的其他threads有Non-daemon threads,那么必须等所有的Non daemon线程结束了,JVM才会停下来。
3. 必须等所有的Non-daemon线程都运行结束了,只剩下daemon的时候,JVM才会停下来,注意Main主程序是Non-daemon线程.
4. 典型的守护线程例子是JVM中的系统资源自动回收线程, 我们所熟悉的Java垃圾回收线程就是一个典型的守护线程。
5. setDaemon需要在start方法调用之前使用。
6. 线程划分为用户线程和后台(daemon)进程,setDaemon将线程设置为后台进程
7. setDaemon方法把java的线程设置为守护线程,此方法的调用必须在线程启动之前执行。
package org.ygy.exam;
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread() {
public void run() {
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread:Hello World");
}
};
t.setDaemon(true);//在start方法之前调用;默认是false;设为true,设置线程为守护线程,
t.start();
System.out.println("main:Hello World");
}
}这里的守护线程,在run()方法里面睡了1秒,所以在main方法执行完之后,JVM中只有一个守护线程,于是JVM就退出了。所以输出为:main:hello World
jion方法
class Demo implements Runnable
{
public void run()
{
for(int x= 0;x<5;x++)
{
System.out.println(Thread.currentThread().getName()+"..."+x);
}
}
}
class JoinDemo
{
public static void main(String[] args)
{
Demo d = new Demo();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
try
{
t1.join();//t1线程要申请加入进来,运行。然后,主线程等待t1执行完毕。
//临时加入一个线程运算时可以使用join方法。
}
catch (InterruptedException e)
{
e.printStackTrace();
}
t2.start();
for(int x = 0;x<5;x++)
{
System.out.println(Thread.currentThread().toString()+"..."+x);
}
}
}setPriority方法:
用法:
ThreadDeme td1 = new ThreadDemo();
ThreadDemo td2 = new ThreadDemo();
td1.setPriority(1);
td2.setPriority(10);
td1.start();
td2.start();
toString方法:
返回改线程的字符串形式,包括线程名称、优先级和线程组。
yield方法:
这个方法是线程方法,当一个线程抢到执行权后,执行到yield()方法后,就会放弃执行权,其他线程就可以拿到执行权 了。 Thread.yield()方法作用是:暂停当前正在执行的线程对象,并执行其他线程。 yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。 结论:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。 没有加Thread.yield()运行结果![]()
有加Thread.yield()运行结果

被折叠的 条评论
为什么被折叠?



