---------------------- ASP.Net+Android+IOS开发、.Net培训、期待与您交流! ----------------------
一.线程概述
* 学习笔记:
* 1、当多个程序例如QQ、暴风、音乐同时运行时,其实CPU是一个一个的执行的,只不过进行的快速切换
* 2、例如,搬东西,五个人一起把A区的一批货物搬到B区,这就是一个进程,而每个人搬运就是线程
* 3、java中进程就是正在执行的程序,而线程就是进程中的一个独立单元,控制着进程的执行
* 4、JVM执行的时候会有一个进程java.exe
* 5、创建线程有两种方法
二.创建线程第一种方法(继承Thread类)
* 1、定义类并继承Thread类
* 2、复写run方法
* 目的:为了将自定义代码存储在run方法中,让线程运行
* 3、创建此类对象,也就是创建了一个线程
* 4、调用start方法,也就是启动了这个线程
* start方法的两个作用:启动线程和调用run方法
* 5、发现每次运行结果都不一样,因为多个线程都在获取CPU的执行权,CPU执行谁,谁就运行
* 注意:在某一个时刻,只有一个线程在运行(多核除外),只不过CPU在做着快速的切换
* 6、多线程运行可以形象的理解为程序在抢夺CPU的执行权
* 这是多线程的一个特性:随机性
代码示例:
public class ThreadDemo {
public static void main(String[] args) {
Test t = new Test(); //创建了一个线程
t.start(); //启动了这个线程,调用run方法
t.run(); //不启动线程,仅仅调用run方法
for(int i =0;i<100;i++){
System.out.println("主函数在执行"+i);
}
}
}
class Test extends Thread{
public void run(){
for(int i = 0;i<100;i++){
System.out.println("复写了run方法"+i);
}
}
}
三.创建线程第二种方法(实现Runnable接口)
* 线程第二种方式:实现Runable接口
* 示例:多窗口卖票
* 步骤;
* 1、定义类实现Runnable接口
* 2、覆盖Runnable接口中的run方法
* 作用:将线程要运行的代码存储在run方法中
* 3、通过Thread类建立线程对象
* 4、把Runnable接口的子类对象作为参数传递给Thread类的构造函数
* 作用:调用run方法的对象必须是Runnable接口的子类对象,所以要把此对象传递给Thread类的构造函数
* 5、调用Thread类的start方法,并调用Runnable接口的子类的run方法
代码示例:
public class RunnableDemo {
public static void main(String[] args) {
Ticket t = new Ticket(); //这并没有创建线程,因为该类没有继承Thread类
Thread t1 = new Thread(t,"一"); //这才是创建了一个线程
Thread t2 = new Thread(t,"二");
Thread t3 = new Thread(t,"三");
Thread t4 = new Thread(t,"四");
Thread t5 = new Thread(t,"五");
t1.start();//启动线程并调用run方法
t2.start();
t3.start();
t4.start();
t5.start();
}
}
class Ticket implements Runnable{
private int i=100; //共有100张票,因为只创建了一个对象,所以i只有一个,是共享的
public void run(){
for(int a=1;a<=1000;a++){
if(i>0){
System.out.println(Thread.currentThread().getName()+"号窗口卖"+i--+"号票");
}
}
}
}
四.继承和实现的区别
* 继承和实现的区别:
* 继承只能够继承一个类,有局限性
* 而如果一个学生类已经继承了人类,那么就不能给继承Thread类了,导致不能够创建线程
* 而实现恰好打破了这种局限性,当一个类已经有了自己的父类,这时仍然可以实现Runnable创建线程
*
* 继承中,线程代码存放在Thread子类的run方法中
* 实现中,线程代码存放在Runnable子类的run方法中
*
* 实现的好处:避免了单继承的局限性
五.多线程练习
* 创建两个线程,并和主线程同时运行
* 1、自定义线程名称
* 线程默认名称为:Thread-0,编号从零开始
* 通过调用父类构造函数对name进行初始化,
* 并定义this.getName()或者(Thread.currentThread().getName)获取名称
* 2、 static Thread currentThread() 获取当前线程对象
* getName() 获取线程名称
* setName() 设置线程名称
代码示例:
public class Thread_Lianxi {
public static void main(String[] args) {
Demo d1 = new Demo("one"); //创建一个线程
Demo d2 = new Demo("two"); //创建一个新线程
d1.start();
d2.start();
for(int i=0;i<30;i++){
System.out.println("bbbbbbbbbbbb");
}
}
}
class Demo extends Thread{
Demo(String name){
super(name);
}
public void run(){
for(int i=0;i<30;i++){ //局部变量i在每个线程区域中都有一个独立的块
System.out.println("aaaaaaaaaaaaaaa"+Thread.currentThread().getName());
}
}
}
六.多线程安全问题
* 多线程安全问题:
* 问题原因:当多个线程在执行同一个线程共享数据时,第一个线程刚刚执行了一部分,
* 另一个线程就参与了进来执行,导致共享数据的错误
* 解决办法:在多个线程操作共享数据时,控制在一个线程执行过程中,其他线程不能干参与执行
*
* java对多线程安全问题提供了专业的解决办法,就是同步代码块
* 格式:
* synchronize(对象){
* 需要被同步的代码块 //只要操作了共享数据的代码就是需要被同步的代码
* }
*
* 相当于火车上的厕所,例如一个人去上厕所,走到门前首先判断下,没人,然后进去,关上门,上锁,在他还没出来之前
* 外面的人看到门上锁显示有人,那么就进不去,只能等里面的人出来以后才能进去
*
* 同步的前提:
* 1、必须有两个或两个以上的线程。例如如果火车上只有一个人,那么就没必要锁门了
* 2、必须是多个线程使用同一个锁。例如如果1号车厢的厕所是打开来,而2号车厢的厕所是锁着来,2号车厢的人还是进不去厕所
*
* 同步的好处:解决了多线程的安全问题
* 同步的弊端:线程执行时需要判断锁,较为耗费资源。 例如回家直接推开门进去和需要开锁,较为麻烦
*
* 补充知识点:实例其实就是对象,但是它是有所属的。比如说,我们可以说他是“人”,
* 但是我们不能单独说他是“儿子”,我们必须说他是某某的“儿子”。
* 所以,我们定义了类 CA,并通过类 CA 创建了对象 objA。
* 我们就可以说 objA 是类 CA 的实例。
代码示例:
public class SynchronizedDemo {
public static void main(String[] args) {
Ticket1 t = new Ticket1(); //这并没有创建线程,因为该类没有继承Thread类
Thread t1 = new Thread(t,"一"); //这才是创建了一个线程
Thread t2 = new Thread(t,"二");
t1.start();//启动线程并调用run方法
t2.start();
}
}
class Ticket1 implements Runnable{
private int i=100; //共有100张票,因为只创建了一个对象,所以i只有一个,是共享的
Object obj = new Object();
public void run(){
for(int a=1;a<=1000;a++){
synchronized(obj){
if(i>0){
try {
Thread.sleep(10); //这个方法本身抛出了异常
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName()+"号窗口卖"+i--+"号票");
}
}
}
}
}
七.多线程同步函数
* 目的:该程序是否有问题,如果由,该怎么找出来
*
* 找问题方法:
* 1、明确哪些代码是多线程运行代码 ( run方法中的代码和所调用的方法)
* 2、明确共享数据 (Bank类的对象、num属性)
* 3、明确多线程运行中哪些代码可以操作共享数据
*
* 然后分析问题:
* 1、如果一个线程在运行到num=num+a后执行权就被其他线程抢走了,这时候就执行不到打印语句了
*
* 解决问题:
* 1、这时候就需要把多线程执行到得共享数据Synchronized了
*
* 同步函数:public Synchronized void add(){
* 同步函数的锁是this
* 同步代码块的锁是对象
* 静态同步函数的锁是 类名.class 字节码文件
代码示例:
public class Synchronized_Cunqian {
public static void main(String[] args){
Client c = new Client();
Thread t1 = new Thread(c);
Thread t2 = new Thread(c);
t1.start();
t2.start();
}
}
//银行
class Bank{
private int num=0;
Object obj = new Object();
public void add(int a){ //同步函数 public Synchronized void add(int a){
synchronized(obj){
num=num+a;
try{Thread.sleep(10);}catch(Exception e){}
System.out.println("现在银行有"+num+"元钱");
}
}
}
//客户存钱
class Client implements Runnable{
Bank b = new Bank();
public void run(){
for(int i=0;i<3;i++){
b.add(100);
}
}
}
八.线程间通信
* 多线程通信:其实就是多个线程在操作同一个资源,只不过操作的动作不同
* 示例:一个线程向共享区存入人的姓名和年龄,而另一个线程向共享区提取数据
*
* 记住:多线程出现了问题;检查两个条件是否满足
* 1、是否同步了两个或两个以上的线程
* 2、必须是多个线程使用同一个锁
代码示例:
public class Thread_tongxin {
public static void main(String[] args) {
Person p = new Person();
Input i = new Input(p);
Output o = new Output(p);
Thread t = new Thread(i);
Thread t1 = new Thread(o);
t.start();
t1.start();
}
}
// 人的属性
class Person {
String name;
String sex;
}
// 存入
class Input implements Runnable {
private Person p;
Input(Person p) {
this.p = p;
}
public void run() {
int i=0;
while (true) {
synchronized (p) {
if (i==0) {
p.name = "小花";
p.sex = "女";
}
else {
p.name = "李四";
p.sex = "男";
}
i = (i+1)%2;
}
}
}
}
// 提取
class Output implements Runnable {
private Person p;
Output(Person p) {
this.p = p;
}
public void run() {
while (true) {
synchronized (p) {
System.out.println(p.name + "........" + p.sex);
}
}
}
}
九.等待唤醒机制
* 多线程中等待唤醒机制
*
* 问题:目前的代码执行后存入数据代码可能会被执行很多次以后,提取数据的代码才能被执行
*
* 目的:想要每存入一组数据,就提取改组数据,然后再存入
* 方法:
* 1、首先定义一个控制共享区的变量,如果为true,就代表里面有数据;如果为false,就代表里面没数据
* 当一个线程开始执行存入代码时,判断下共享区里面是否有数据(是否为false),如果没有就存入,有的话就wait
* 存入以后改变这个变量改为true,并且唤醒提取代码的线程notify,
* 2、现在提取代码的线程已经被唤醒,首先if判断共享区里面是否有数据(是否为true),如果由就提取,没有就wait
* 提取出以后改变这个变量为false,并且唤醒存入代码的线程notify,
*
*
* 注意:
* 1、不管是存入还是提取,锁都要一样,唯一性
* 2、因为wait()方法、notify()方法都是Object中的方法,所以需要对象进行调用,因为是Object中的方法,所以任何对象都可以
* 3、wait()方法在Object类中抛出了异常,所以需要在内部进行捕获处理
代码示例:
public class Deng_dai_huan_xing_ji_zhi {
public static void main(String[] args) {
Person p = new Person();
// Input i = new Input(p);
// Output o = new Output(p);
// Thread t = new Thread(i);
// Thread t1 = new Thread(o);
new Thread(new Input(p)).start();
new Thread(new Output(p)).start();
}
}
// 人的属性
class Person {
private String name;
private String sex;
private boolean flag = false;
public void setShow(String name, String sex) {
synchronized (this) {
if (this.flag) {
try {
this.wait();
} catch (Exception e) {
}
}
this.name = name;
this.sex = sex;
this.flag = true;
this.notify();
}
}
public void print() {
synchronized (this) {
if (!this.flag) {
try {
this.wait();
} catch (Exception e) {
}
}
System.out.println(this.name + "........" + this.sex);
this.flag = false;
this.notify();
}
}
}
// 存入
class Input implements Runnable {
private Person p;
Input(Person p) {
this.p = p;
}
public void run() {
int i = 0;
while (true) {
if (i == 0) {
p.setShow("小花", "女");
} else {
p.setShow("小黑", "男");
}
i = (i + 1) % 2;
}
}
}
// 提取
class Output implements Runnable {
private Person p;
Output(Person p) {
this.p = p;
}
public void run() {
while (true) {
p.print();
}
}
}
十.等待唤醒机制实例(生产者与消费者)
* 多线程唤醒机制例子:生产者与消费者
*
* 1、为什么要使用while
* :因为如果当生产者多线程被唤醒以后,共享区里明明有数据,这时候没有经过判断就又创建了一个商品
* 共享区内就会发生错误
* 2、为什么使用notifyAll
* :因为当生产者线程wait以后,可能会唤醒其他生产者线程,因为需要先经过判断,这样也生产不了,
* 导致所有线程全部等待
代码实例:
public class Produce_Consume {
public static void main(String[] args) {
Resource r = new Resource();
Produce p = new Produce(r); //为了保证r对象是同一个
Consume c = new Consume(r); //为了保证r对象是同一个
Thread t1 = new Thread(p);
Thread t2 = new Thread(p);
Thread t3 = new Thread(c);
Thread t4 = new Thread(c);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
//共享资源
class Resource{
private String name; //商品名称
private int count=1; //计数
private boolean flag= false;
//生产
public synchronized void intPut(String name){
while(flag){
try{
wait();
}catch(Exception e){
}
}
this.name = name;
count++;
System.out.println(Thread.currentThread().getName()+this.name+this.count);
flag=true;
this.notifyAll();
}
//消费
public synchronized void OutPut(){
while(!flag){
try{
wait();
}catch(Exception e){
}
}
System.out.println(Thread.currentThread().getName()+this.name+this.count+"...............");
flag=false;
this.notifyAll();
}
}
//生产者
class Produce implements Runnable{
private Resource r;
Produce(Resource r){
this.r = r;
}
public void run(){
while(true){
//
r.intPut("面包");
}
}
}
//消费者
class Consume implements Runnable{
private Resource r;
Consume(Resource r){
this.r = r;
}
public void run(){
while(true){
r.OutPut();
}
}
}
十一.lock锁(消费者与生产者代码更严谨)
* 思路:
* 1、因为在JDK1.5以后,lock就替换了synchronized,而condition就替换了wait()、notify();
* 2、现在通过lock.lock()的方式就可以手动上锁,通过lock.unlock()的方式可以手动开锁
* 3、通过con.await()的方式让线程等待,注意,这时候会抛出一个异常,而这个异常不能够在这里处理,所以要声明
* 4、通过con.signalAll()的方式唤醒全部线程
*
*
* 可是这样又出现了问题:
* 因为con.signalAll()是唤醒全部线程,那么本方线程也可能去读取while,这样就占用了资源
*
* 解决办法:
* 1、在这里可以通过创建多个Condition实例,
* 2、此例创建两个,一个控制唤醒消费者线程,另一个控制唤醒生产者线程
代码示例:
public class Produce_Consume {
public static void main(String[] args) {
Resource_ r = new Resource_();
Produce_ p = new Produce_(r);
Consume_ c = new Consume_(r);
/*
* Thread有一个构造函数 Thread(Runnable r),作用是分配新的Thread对象
* 因为只有Thread对象才可以启动线程并调用run方法
*/
Thread t1 = new Thread(p);
Thread t2 = new Thread(p);
Thread t3 = new Thread(c);
Thread t4 = new Thread(c);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
// 资源
class Resource_ {
private String name;
private int count = 1;
private boolean flag = false;
private Lock lock = new ReentrantLock(); // (多态)通过Lock接口的实现类ReentrantLock创建了lock对象
private Condition con_pro = lock.newCondition(); // lock对象通过调用newCondition方法创建了绑定此Lock实例的Condition实例con
private Condition con_con = lock.newCondition();
// 生产
public void produce_(String name) throws Exception {
lock.lock();
try {
while (flag) {
con_pro.await(); // 这里不能处理,因为只有当为true时才会等待,那么此时线程处于等待状态,怎么可能继续运行处理代码呢?
}
this.name = name + "...." + count++;
System.out.println(Thread.currentThread().getName()
+ this.name );
flag = true;
con_con.signal();
} finally {
lock.unlock(); // 因为不管线程是否等待,都必须打开锁,所以把开锁功能放在finally中
}
}
// 消费
public void consume_() throws Exception {
lock.lock();
try {
while (!flag) {
con_con.await();
}
System.out.println(Thread.currentThread().getName()
+ this.name + "...............");
flag = false;
con_pro.signal();
} finally {
lock.unlock();
}
}
}
// 生产者
class Produce_ implements Runnable {
private Resource_ r;
Produce_(Resource_ r) {
this.r = r;
}
public void run() {
while (true) {
try {
r.produce_("+商品+");
} catch (Exception e) {
}
}
}
}
// 消费者
class Consume_ implements Runnable {
private Resource_ r;
Consume_(Resource_ r) {
this.r = r;
}
public void run() {
while (true) {
try {
r.consume_();
} catch (Exception e) {
}
}
}
}
十二.停止线程
如何停止线程?
只有一种,run方法结束。
开启多线程运行,运行代码通常是循环结构。
只要控制住循环,就可以让run方法结束,也就是线程结束。
特殊情况:
当线程处于了冻结状态。
就不会读取到标记。那么线程就不会结束。
当没有指定的方式让冻结的线程恢复到运行状态是,这时需要对冻结进行清除。
强制让线程恢复到运行状态中来。这样就可以操作标记让线程结束。
Thread类提供该方法 interrupt();
代码示例:
class StopThread implements Runnable
{
private boolean flag =true;
public synchronized void run()
{
while(flag)
{
try{
wait();
}catch(Exception e){
System.out.println(Thread.currentThread().getName()+"....Exception");
flag = false;
}
System.out.println(Thread.currentThread().getName()+"....run");
}
}
}
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 = 0;
while(true)
{
if(num++ == 60)
{
//st.changeFlag();
t1.interrupt();
t2.interrupt();
break;
}
System.out.println(Thread.currentThread().getName()+"...."+num);
}
System.out.println("over");
}
}
十三.守护线程
* 守护线程(又叫用户线程、后台线程)
* 注意:
* 1、该方法必须在启动线程之前调用
* 2、如果只有守护线程在运行,那么JVM退出
*
* 这时候程序中线程t1和t2因为被定义为守护线程,当它们都wait时,主线程执行完毕,JVM退出,程序执行完毕
代码示例:
public class SetDaemon {
public static void main(String[] args)
{
StopThread st = new StopThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
//定义守护线程
t1.setDaemon(true);
t2.setDaemon(true);
t1.start();
t2.start();
int num = 0;
while(true)
{
if(num++ == 60)
{
//st.changeFlag();
//t1.interrupt();
//t2.interrupt();
break;
}
System.out.println(Thread.currentThread().getName()+"...."+num);
}
System.out.println("over");
}
}
class StopThread implements Runnable
{
private boolean flag =true;
public synchronized void run()
{
while(flag)
{
try{
wait();
}catch(Exception e){
System.out.println(Thread.currentThread().getName()+"....Exception");
flag = false;
}
System.out.println(Thread.currentThread().getName()+"....run");
}
}
}
十四.join方法
join:
当A线程执行到了B线程的join()方法时,A就会等待。等B线程都执行完,A才会执行。
join可以用来临时加入线程执行。
代码示例:
class Demo1 implements Runnable
{
public void run()
{
for(int x=0; x<70; x++)
{
System.out.println(Thread.currentThread().getName()+"....."+x);
}
}
}
class JoinDemo
{
public static void main(String[] args) throws Exception
{
Demo1 d = new Demo1();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
//t1.join(); //这句话的意思是:当主线程执行到这里时,会释放执行权,因为只有当t1执行完以后主线程才能重新获得执行权
t2.start();
t1.join(); /*这句话的意思是:当主线程执行到这里时,会释放执行权,
*而因为之前已经有t1和t2两个线程都被启动,所以这时候t1和t2两个线程争夺执行权,
*当t1执行完以后主线程才可以拥有执行权,这时候就是主线程和t2争夺执行权了
*/
for(int x=0; x<80; x++)
{
System.out.println("main....."+x);
}
System.out.println("over");
}
}
十五.优先级&yield方法
* toString()方法:返回该线程的字符串表示形式,包括 【线程名称】【优先级】【线程组】
*
* 对于优先级,更改线程的优先级格式:
* setPriority(MAX_PRIORITY 或 MIN_PRIORITY 或 NORM_PRIORITY);
代码示例:
public class ToString_Demo {
public static void main(String[] args) throws Exception
{
Demo2 d = new Demo2();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
t1.setPriority(Thread.MAX_PRIORITY);
//MAX_PRIORITY是10 ; MIN_PRIORITY是1 ; ONRM_PRIORITY是5
t2.start();
for(int x=0; x<80; x++)
{
System.out.println("main....."+x);
}
System.out.println("over");
}
}
class Demo2 implements Runnable
{
public void run()
{
for(int x=0; x<70; x++)
{
System.out.println(Thread.currentThread().toString()+"....."+x);
}
}
}
* static void yield()
* 作用:暂停执行当前线程,改为执行其他线程
代码示例:
public class YieldDemo {
public static void main(String[] args) throws Exception //异常抛给了JVM
{
Demo2 d = new Demo2();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
t2.start();
}
}
class Demo3 implements Runnable
{
public void run()
{
for(int x=0; x<70; x++)
{
System.out.println(Thread.currentThread().toString()+"....."+x);
Thread.yield();
}
}
}
---------------------- ASP.Net+Android+IOS开发、.Net培训、期待与您交流! ----------------------详细请查看:http://edu.csdn.net