Java多线程笔记

**

Java多线程笔记

**

java中线程实现的方式
在java中实现多线程有两种手段,一种是集成Thread类,另一种就是实现Runnable接口。下面我们就分别来介绍这两种方式的使用。
实现Runnable接口

package com.kaikeba.unitfore.lessonfive;

class MyThread implements Runnable{
    private String name;//表示线程的名称
    public MyThread(String name){
        this.name = name;//通过构造方法配置name属性
    }

    @Override
    public void run() { //覆写run()方法,作为线程的操作主体
        for(int i=0;i<10;i++){
            System.out.println(name + "运行,i=" + i);
        }
    }
}
public class RunnableDemo01{
    public static void main(String[] args) {
        MyThread mt1 = new MyThread("线程A ");//实例化MyThread对象,mt1
        MyThread mt2 = new MyThread("线程B ");//实例化MyThread对象,mt2
        Thread t1 = new Thread(mt1);//实例化Thread类对象,t1
        Thread t2 = new Thread(mt2);//实例化Thread类对象,t2
        //启动多线程
        t1.start();
        t2.start();
    }
}

程序运行结果:
线程A 运行,i=0
线程B 运行,i=0
线程A 运行,i=1
线程B 运行,i=1
线程A 运行,i=2
线程B 运行,i=2
线程A 运行,i=3
线程B 运行,i=3
线程A 运行,i=4
线程B 运行,i=4
线程A 运行,i=5
线程B 运行,i=5
线程A 运行,i=6
线程B 运行,i=6
线程A 运行,i=7
线程B 运行,i=7
线程A 运行,i=8
线程A 运行,i=9
线程B 运行,i=8
线程B 运行,i=9

集成Thread类

package com.kaikeba.unitfore.lessonfive;
class Mythread extends Thread{//集成Thread类,作为线程的实现类
    private String name;//线程名称
    public Mythread(String name){
        this.name = name;//构造方法,配置name属性
    }
    @Override
    public void run(){
        for(int i=0;i<10;i++){
            System.out.println(name + "运行,i= " + i);
        }
    }
}
public class ThreadDemo02 {
    public static void main(String[] args) {
        //实例化MyThread类对象
        Mythread mt1 = new Mythread("线程A ");
        Mythread mt2 = new Mythread("线程B ");
        //调用线程主体
        mt1.start();
        mt2.start();
    }
}

程序运行结果:
线程B 运行,i= 0
线程A 运行,i= 0
线程B 运行,i= 1
线程A 运行,i= 1
线程B 运行,i= 2
线程A 运行,i= 2
线程B 运行,i= 3
线程A 运行,i= 3
线程B 运行,i= 4
线程A 运行,i= 4
线程B 运行,i= 5
线程A 运行,i= 5
线程B 运行,i= 6
线程A 运行,i= 6
线程B 运行,i= 7
线程A 运行,i= 7
线程B 运行,i= 8
线程A 运行,i= 8
线程B 运行,i= 9
线程A 运行,i= 9

从程序可以看出,现在两个线程对象是交错运行的,哪个线程对象抢到了CPU资源,哪个线程就可以运行,所以程序每次的运行结果肯定不一样,在线程启动虽然调用的是start()方法,但实际上调用的却是run()方法的内容。
Thread类和Runnable接口
通过Thread类和Runnable接口都可以实现多线程,name两者有哪些联系和区别?下面我们观察Thread类的定义

public class Thread extends Object implements Runnable

从Thread类的定义可以清楚地发现,Thread类也是Runnable接口的子类,但在Thread类中并没有完全实现Runnable接口中的run()方法,下面是Thread类的部分定义。

/* What will be run. */
private Runnable target;
public Thread(Runnable target) {
        this(null, target, "Thread-" + nextThreadNum(), 0);
}
private Thread(ThreadGroup g, Runnable target, String name,
                   long stackSize, AccessControlContext acc,
                   boolean inheritThreadLocals) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name;

        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            /* Determine if it's an applet or not */

            /* If there is a security manager, ask the security manager
               what to do. */
            if (security != null) {
                g = security.getThreadGroup();
            }

            /* If the security manager doesn't have a strong opinion
               on the matter, use the parent thread group. */
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }

        /* checkAccess regardless of whether or not threadgroup is
           explicitly passed in. */
        g.checkAccess();

        /*
         * Do we have the required permissions?
         */
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(
                        SecurityConstants.SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }

        g.addUnstarted();

        this.group = g;
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target;
        setPriority(priority);
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        this.tid = nextThreadID();
}
@Override
public void run() {
   if (target != null) {
      target.run();
   }
}

从定义中可以发现,在Thread类中的run()方法调用的时Runable接口中的run()方法,也就是说此方法是由Runnable子类完成的,所以如果要通过继承Thread类实现多线程,则必须覆写run()。

实际上Thread类和Runnable接口之间在使用上也是有区别的,如果一个类继承Thread类,则不适合于多个线程共享资源,而实现了Runnable接口,就可以方便的实现资源共享。

线程的状态变化
要想实现多线程,必须在主线程中创建新的线程对象。任何线程一般具有6种状态,即NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED。
下面介绍下这些状态:

状态描述
NEW尚未启动的线程
RUNNABLE可运行线程
BLOCKED线程被阻塞,等待监视器锁定
WAITING等待状态
TIMED_WAITING具有指定等待时间的等待线程
TERMINATED终止的线程状态

同步以及死锁
一个多线程的程序,如果是通过Runnable接口实现的,则意味着类中的属性被多个线程共享,name这样就会造成一种问题,如果多个线程要操作同一个资源时,就有可能出现资源同步的问题。
解决方法:

  • 同步代码块
synchronized(同步对象){
需要同步的代码
}
package com.kaikeba.unitfore.lessonfive;
class MyThread implements Runnable{
    private int ticket = 5;//假设一共有5张票
    @Override
    public void run() {
        for(int i=0;i<100;i++){
            synchronized (this){
                if(ticket>0){
                    try {
                        Thread.sleep(300);
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                    System.out.println("卖票:ticket= " + ticket--);
                }
            }
        }
    }
}
public class SyncDemo {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        Thread t1 = new Thread(mt);
        Thread t2 = new Thread(mt);
        Thread t3 = new Thread(mt);
        t1.start();
        t2.start();
        t3.start();
    }
}

程序执行结果:
卖票:ticket= 5
卖票:ticket= 4
卖票:ticket= 3
卖票:ticket= 2
卖票:ticket= 1

  • 同步方法
    除了可以将需要的代码设置成同步代码块外,也可以用synchronized关键字将一个方法声明为同步方法。
synchronized 方法返回值 方法名称(参数列表){
}
package com.kaikeba.unitfore.lessonfive;
class MyThread implements Runnable{
    private int ticket = 5;

    @Override
    public void run() {
        for(int i=0;i<100;i++){
            this.sale();
        }
    }

    private synchronized void sale() {
        if(ticket>0){
            try{
                Thread.sleep(300);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println("卖票: ticket = " + ticket--);
        }
    }
}
public class SyncDemo {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        Thread t1 = new Thread(mt);
        Thread t2 = new Thread(mt);
        Thread t3 = new Thread(mt);
        t1.start();
        t2.start();
        t3.start();
    }
}

执行结果:
卖票: ticket = 5
卖票: ticket = 4
卖票: ticket = 3
卖票: ticket = 2
卖票: ticket = 1

从程序运行结果可以发现,此代码完成了与之前同步代码同样的功能。

死锁
同步可以保证资源共享操作的正确性,但是过多同步也会产生问题。例如,现在张三想要李四的画,李四想要张三的书,张三对李四说“把你的画给我,我就给你书”,李四也对张三说“把你的书给我,我就给你画”。两个人互相等待对方先行动,就可能会出现没有结果的现象,这就是死锁的概念。
所谓死锁,就是两个线程都等对方先完成,早晨该程序停滞,一般程序的死锁都是在程序运行时出现的。
下面一个简单的范例说明此概念:

package com.kaikeba.unitfore.lessonfive;
class ZhangSan{
    public void say(){
        System.out.println("张三对李四说:“你给我画,我给你书。");
    }
    public void get(){
        System.out.println("张三得到画。");
    }
}
class LiSi{
    public void say(){
        System.out.println("李四对张三说:“你给我书,我给你画。”");
    }
    public void get(){
        System.out.println("李四得到书。");
    }
}
public class ThreadDeadLock implements Runnable{
    private static ZhangSan zs = new ZhangSan();
    private static LiSi ls = new LiSi();
    private boolean flag = false;//声明标志位,判断哪个先说话
    @Override
    public void run() {
        if(flag){
            synchronized (zs){
                zs.say();
                try {
                    Thread.sleep(500);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
                synchronized (ls){
                    zs.get();
                }
            }
        }else{
            synchronized (ls){
                ls.say();
                try {
                    Thread.sleep(500);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
                synchronized (zs){
                    ls.get();
                }
            }
        }
    }

    public static void main(String[] args) {
        ThreadDeadLock t1 = new ThreadDeadLock();//控制张三
        ThreadDeadLock t2 = new ThreadDeadLock();//控制李四
        t1.flag = true;
        t2.flag = false;
        Thread thA = new Thread(t1);
        Thread thB = new Thread(t2);
        thA.start();
        thB.start();
    }
}

程序运行结果:
张三对李四说:“你给我画,我给你书。”
李四对张三说:“你给我书,我给你画。”

一下代码不在执行,程序进入死锁状态。
总结
本人现在了解的多线程操作介绍完毕,如有缺漏,欢迎相互交流学习。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值