黑马程序员之-------------java多线程

来源:互联网 发布:php断点续传 上传 编辑:程序博客网 时间:2024/06/04 19:44


------<a href="http://www.itheima.com" target="blank">Java培训、Android培训、iOS培

训、.Net培训</a>、期待与您交流! -------


---------------Java之------多线程----------------

多线程状态图

 

 

1.进程:正在执行的程序。一个进程中至少有一个线程

           每一个进程执行都有一个执行顺序(执行路径也叫控制单元)

 

2.线程:就是进程中的一个独立的控制单元。线程在控制着进程的执行。

多线程:就是一个进程中有多个线程(运行路径)

    多线程允许多个线程并发执行,事实上,在某一个时刻,只能有一个线程在运行。cpu在做着快速的切换,以达到看上去是同时运行的效果。

 

3.Java JVM 启动时进程java.exe运行。该进程中的主线程启动(存在于main方法中)

   

4.创建进程的方法:

 (1)第一种方法:继承Thread类

   步骤:1.定义一个类继承Thread类  

         2.复写Thread类中的run方法(目的:将自定义代码存储在run方法中,让线程运行)

  建立子类对象的同时线程也被创建。run方法中定义就是线程要运行的任务代码。

     3.调用线程的start方法(该方法有两个作用:启动线程;调用run方法)

Thread类用于描述线程,该类就定义了一个功能,用于存储线程要运行的代码,该存储功能就是run方法,也就是说Thread类中的run方法,用于存储线程要运行的代码。因此,我们创建一个线程时,要覆写run方法

 

class A extends Thread{//继承Thread类

private String name;

A(String name){

super(name);//给线程取名

}

public void run(){//覆盖Thread类中的run方法。

for(int x=0;x<10;x++){

System.out.println(x+"--"+

Thread.currentThread().getName());//获取线程的名称

}

}

}

public class Demo  { 

 public static void main(String[] args)   { 

 A a1=new A("zhangsan");

 A a2=new A("lisi");

 a1.start();//开启线程

 a2.start();

 for(int x=0;x<20;x++){

 System.out.println(x+"==="+Thread.currentThread().getName());

 }

 

 }

}

 

 (2)第二种方法:声明实现Runnable接口(重点)

    步骤:

     1·定义类实现Runnable接口

class T implements Runnable

     2·覆盖Runnable接口中的run方法将线程要运行的代码存放在该run方法中

     3·通过Thread类建立线程对象

     4·将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数

因为自定义的run方法所属的对象是Runnable接口的子类对象,

要让线程执行对象的run方法,就必须明确该run方法所属对象

      5·调用Thread类的start方法开启线程,并调用Runnable接口子类的run方法

 

在定义线程时,建议使用实现方式,因为实现方式避免了单继承的局限性。

不同:

第一种方法存在Thread子类的run方法

而runnable存在接口子类的run方法中

 

/* 买票*/

class Ticket implements Runnable {

// 接口的子类发生异常,要try.处理不了,交给执行者处理

int num = 100;

//Object obj=new Object();//在Ticket类里面,相等于成员变量

public void run() {//run方法是覆写Runnable接口的方法,如果

//Object obj=new Object();放在里面,new了4次,等于有了4把锁

while (true) {

//synchronized(obj){

if (num > 0) {

try {

Thread.sleep(10);

catch (InterruptedException e) {

}

System.out.println(Thread.currentThread().getName()

"--sale--" + num--);

//}

}

}

}

}

//结果多个窗口卖同一张票

public class hh {

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();

}

}

 

5.线程常用方法:

Thread currentThread():获取当前线程对象。

getName(): 获取线程名称。

设置线程名称:setName或者构造函数。

Test(String name){

super(name);

}

Sleep():控制线程休眠,单位为毫秒。

一个线程不能开启两次,会抛出无效线程状态异常

 

6.多线程的安全问题

 多条语句执行同一个线程共享数据时,一个线程对多条语句

 只执行一部分,还没有执行完,另一个线程参与进来执行导致

 共享数据错误

 

 判断是否存在安全隐患(重点)

 查看覆写run方法的代码中是否存在共享数据。

 run方法中调用的方法是否是赋值的add(int num)其中num就是共享数据。

 并且对于共享数据的操作不止一条。这时就存在安全隐患。

 

 解决办法:

 对操作多条数据的语句,只能让一个线程先执行完,其他线yield()程在执行

 在执行工程中,其他线程不可以参与执行。java对于多线程的安全问题,

 指定了专业的解决方式-----------就是"同步代码块"。

 

7.同步代码块

  // 这里对象就是使用的锁 Object obj=new Object();

 synchronized(任意的对象obj) 

   {

    需要被同步的代码; (注意 :只把需要同步的代码放进来,而不是把run方法中所有代码都同步)

   }

 

8.同步函数

 用synchronized 作为修饰符来修饰函数:叫做同步函数

 同步函数用的锁是this。

 建议使用同步代码块。

/**

 *银行存钱:  

 */

class Bank{

private int sum;

//Object obj=new Object();

public synchronized void add(int num){//同步函数的锁是this

//synchronized(obj){

sum=sum+num;

System.out.println("sum="+sum);

}

}

}

class Cus implements Runnable{

private Bank b=new Bank();

public void run(){

for(int i=0;i<3;i++){

b.add(100);

}

}

}

public class Test{

public static void main(String[] args){ 

Cus c=new Cus();

Thread t1=new Thread(c);

Thread t2=new Thread(c);

t1.start();

t2.start();

}

}

 

9.同步的前提(重点)

 (1)对象如同锁,持有锁的线程可以在同步中执行。(锁就是一个对象)

    没有持有锁的线程即使获得cpu的执行权,也进不去,因为没有获取锁

 

 (2)同步的前提

·必须要有两个或者两个以上的线程

     ·必须是多个线程使用同一个锁

     ·必须保证同步中只能有一个线程在运行

 

 (3)同步的好处和弊端:

·好处 :解决了多线程的安全问题

·弊端 :多个线程需要判断锁,较为消耗资源

 

 (4)锁

  Lock是一个接口,它其中有获取锁和解锁的方法 :替代了同步代码块或者同步函数。

将同步的隐式锁操作变成现实锁操作。 可以一个锁上加上多组监视器。

1.void lock() -- 获取锁

2.void unlock() -- 释放锁

3.newCondition() -- 返回绑定到此Lock实例的新Condition对象 (具有wait,notify的功能)

   Condition

 |-- await()  代替了 wait()

|-- signal()  代替了 notify()

|-- signalAll()  代替了 notifyAll() 

 

将Synchronized替换成了Lock操作,

     将Object类中的wait,notify,notifyAll替换成了Condition对象,该对象可以通过Lock锁进行获取。

     实现了本方只唤醒对方的操作

import java.util.concurrent.locks.*;

 

class Resource {

private String name;

private int count = 1;

private boolean flag = false;

// 创建一个锁对象。

Lock lock = new ReentrantLock();

// 通过已有的锁获取该锁上的监视器对象。

// Condition con = lock.newCondition();

// 通过已有的锁获取两组监视器,一组监视生产者,一组监视消费者。

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) {

}

this.name = name + count;

 

count++;

System.out.println(Thread.currentThread().getName()

"...生产者5.0..." + this.name);

 

flag = true

// notifyAll();

// con.signalAll();

consumer_con.signal();

finally {

lock.unlock();

}

}

 

public void out()

{

lock.lock();

try {

while (!flag)

try {

consumer_con.await();

catch (InterruptedException e) {

System.out.println(Thread.currentThread().getName()

"...消费者.5.0......." + this.name);

 

flag = false;

notifyAll();

// con.signalAll();

producer_con.signal();

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 ProducerConsumerDemo2 {

public static void main(String[] args) {

Resource r = new Resource();

Producer pro = new Producer(r);

Consumer con = new Consumer(r);

Thread t0 = new Thread(pro);

Thread t1 = new Thread(pro);

Thread t2 = new Thread(con);

Thread t3 = new Thread(con);

t0.start();

t1.start();

t2.start();

t3.start();

}

}

 

 

10. 同步函数使用的是哪一个锁呢?

   函数需要被对象调用,那么函数都有一个所属对象引用,就是this

   所以同步函数使用的锁是this

 

11. 静态同步方法中使用的锁是:类名.class

   因为静态方法中不可以定义this。静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。

   即 :类名.class 或this.getclass, 该对象的类型是 :Class

 

12. 死锁 :同步中嵌套同步。两个线程持有的锁不同,而要相互访问,就有可能出现死锁情况.

 

class ThreadDemo implements Runnable {

//private boolean flag;

//ThreadDemo(boolean flag){

//this.flag=flag;

//}

private int num=1;

ThreadDemo(){

}

ThreadDemo(int num){

this.num=num;

}

public void run() {

//while(true)

//if (flag) {

if (num==1) {

synchronized (Lock.locka) {

System.out.println(Thread.currentThread().getName()

"if---locka");

synchronized (Lock.lockb) {

System.out.println(Thread.currentThread().getName()

"if---lockb");

}

}

else {

//while(true)

synchronized (Lock.lockb) {

System.out.println(Thread.currentThread().getName()

"else---lockb");

synchronized (Lock.locka) {

System.out.println(Thread.currentThread().getName()

"else---locka");

}

}

}

}

}

 

class Lock {

public static final Object locka = new Object();

public static final Object lockb = new Object();

}

 

public class hh {

public static void main(String[] args) {

//ThreadDemo a = new ThreadDemo(true);

//ThreadDemo b = new ThreadDemo(false);

ThreadDemo a = new ThreadDemo();

ThreadDemo b = new ThreadDemo(2);

Thread t1 = new Thread(a);

Thread t2 = new Thread(b);

t1.start();

t2.start();

}

}

 

13.多线程间的通信

 

 

 1. wait();   notify();   notifyall();

    都是用在同步中。因为要对持有锁的线程操作,而只有同步中才有锁。

 

 2.wait()和sleep()的区别 :

   wait()释放资源,释放锁

   sleep()释放资源,不释放锁

 

 3.为什么这些操作线程的方法要定义在Object类中呢?

    因为这些方法在操作同步线程时,都必须要标识它们所操作的线程持有的锁,

    只有同一个锁上的被等待线程可以被同一个锁上的notify唤醒。

    也就是说,wait和notify必须是同一个锁

    而锁可以是任意对象,所以可以被任意对象调用的方法定义在Object类中

 

 4. join() : 当A线程执行到了B线程的join()方法时,A就会等待B线程执行完毕,A才会执行

     join()可以用来临时加入线程执行

 

 5. 优先级 -- priority

    setPrority -- 设置优先级

      ·MAX_PRIORITY 最高优先级

      ·MIN_PRIORITY 最低优先级

      ·NORM_PRIORITY 默认优先级

 

    如:t1.setPriority(thread.MAX_PRIORITY);  t1拥有最高的优先级执行

 

 6. yield() : 暂停当前正在执行的线程对象,并释放执行权,然后再共同争夺执行权(执行其他线程)

 

14.线程的停止

 1.stop方法已经过时,无法使用了。

 2.停止线程只有一种方法 :run方法结束

  因为线程运行代码一般都是循环,只要控制了循环即可。

 

  控制循环通常就用定义标记来完成。

 3.特殊情况 :

    ·当线程处于冻结状态时,就不会读取到标记,那么线程就不会结束。

    ·当没有指定的方式让冻结的线程恢复到运行状态时,就需要对冻结状态进行清除,

      用interrupt方法强制将冻结状态的线程恢复到运行状态(即:获取执行资格)

 4.interrupt方法(中断)

    该方法是结束线程的冻结状态,使线程回到运行状态中来。

 

 5.守护线程 -- setDaemon(boolean on)

    on 如果标记为true,则将线程标记为守护线程(即:SetDamon(true)) 

    当正在运行的线程都是守护线程时,Java虚拟机退出。

    该方法必须在启动线程前调用

 

    守护线程也是后台线程,而主线程等是前台线程,

    后台线程开启后,和前台线程共同抢夺cpu的执行权运行,

    当所有的前台线程都结束后,后台线程就自动结束 

 

 6.getThreadGroup  返回该线程所属的数组

 7.MAX_PRIORITY----最大执行权

   NORM_PRIORITY----默认执行权

   MIN_PRIORITY----最小执行权

 8.setPriority  设置优先级

 9.isDaemon  设置为后台线程

 

 

15.单例模式的安全隐患

 

 恶汉式单例模式

 class Single{

private static Single s=new Single();

private Single(){};

public static Single getSingle(){

return s;

}

 }

 

 懒汉式单例模式

 class Single1 {

private static Single1 s = null;

 

private Single1() {

};

 

public static/* synchronized */Single1 getSingle() {

// 函数加同步解决安全隐患。效率低。锁为类.class

if (s == null) {

//加一次判断,如果不为空,就不用判断同步锁,提高效率

synchronized (Single1.class) {

//同步代码块,解决安全隐患

if (s == null)

s = new Single1();

}

}

return s;

}

}

   

16.测试题

1.------------------------------------

public class ThreadTest {

public static void main(String[] args) {

new Thread(new Runnable() {

public void run() {

System.out.println("runnable run");

}

}) { // 子类覆写父类的run方法,运行子类。如果子类不存在,运行父类。

// 如果都没有,运行Thread。

public void run() {

System.out.println("subThread run");

}

}.start();

}

}


 class Test implements Runnable { 

 public void run(Thread t)  {

}

//如果错误 错误发生在哪一行?错误在第一行,应该被abstract修饰  


 ------<a href="http://www.itheima.com" target="blank">Java培训、Android培训、iOS培

训、.Net培训</a>、期待与您交流! -------

 

 

 

 

 

 

 

 

 

 

0 0
原创粉丝点击