Java 线程的使用

1.线程的基本概念

程序(program)是为完成特定任务、用某种语言编写的一组指令的集合。即指一 段静态的代码,静态对象。

进程(process)是程序的一次执行过程,或是正在运行的一个程序。是一个动态 的过程:有它自身的产生、存在和消亡的过程。——生命周期

如:运行中的QQ,运行中的MP3播放器

程序是静态的,进程是动态的 进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域

线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。

若一个进程同一时间并行执行多个线程,就是支持多线程的

线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开 销小

一个进程中的多个线程共享相同的内存单元/内存地址空间它们从同一堆中分配对象,可以 访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资 源可能就会带来安全的隐患。

  • 关系

进程可以细化为多个线程

每个线程拥有自己独立的:栈区和程序计数器

多个线程共享一个程序中结构:方法区、堆

2.使用多线程的优点

背景:以单核CPU为例,只使用单个线程先后完成多个任务(调用多个方 法),肯定比用多个线程来完成用的时间更短,为何仍需多线程呢? 多线程程序的优点: 1. 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。 2. 提高计算机系统CPU的利用率 3. 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和 修改

3.线程的使用

         继承Thread类的方式

         实现Runnable接口的方式

3.1方式1 Thread方式

1) 定义子类继承Thread类。

2) 子类中重写Thread类中的run方法。

3) 创建Thread子类对象,即创建了线程对象。

4) 调用线程对象start方法:启动线程,调用run方法。

  • 注意点:

1. 如果自己手动调用run()方法,那么就只是普通方法,没有启动多线程模式。

2. run()方法由JVM调用,什么时候调用,执行的过程控制都有操作系统的CPU 调度决定。

3. 想要启动多线程,必须调用start方法。

4. 一个线程对象只能调用一次start()方法启动,如果重复调用了,则将抛出以上 的异常“IllegalThreadStateException”。

  • 代码演示

/**
 * 常用的方法
 * 1.start 启动线程,调度run方法
 * 2.run 线程的执行内容
 * 3.currentThread() 静态方法,返回执行当前代码的线程
 * 4.getName() 获取当前线程的名称
 * 5.setName() 设置当前线程的名称
 * 6.yield() 释放当前cpu的执行权,但是下次一cpu任然可能抢占
 * 7.join() 在线程1调用线程2的join方法,此时线程1进入阻塞状态!!
 *          直到线程2 执行完毕释放了 才结束线程1 的阻塞状态
 * 8.stop() 执行此方法强行结束当前线程
 * 9. sleep(long time) 让线程指定睡眠time时间,阻塞time时间过了继续进行线程任务
 * 10 isAlive() 判断当前线程是否存活
 *
 * **************************
 * 线程的优先级问题
 * 1.
 * MIN_PRIORITY = 1
 * NORM_PRIORITY = 5 (默认优先级)
 * MAX_PRIORITY = 10
 * 2.设置优先级
 * getPriority()获取优先级
 * setPriority(int p) 设置优先级
 *
 * 说明:高优先级的进程会抢占地优先级的cpu资源的执行权力
 *      但是只是意味着高优先级的进程抢占成功率更好不代表高优先级
 *      先执行进程才到低优先级进程执行。
 */
public class demoCreat {
    public static void main(String[] args) throws InterruptedException {
//        3.创建thread类
        MyThread myThread = new MyThread();
        MyThread myThread1 = new MyThread();
        /**
         * 设置优先级
         */
        myThread.setPriority(Thread.MAX_PRIORITY);
        myThread1.setPriority(Thread.MIN_PRIORITY);
        Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
        /**
         * 不可以直接调用run方法这样不是线程的启动方式
         * 。start的作用就是  1.启动线程 2.调用当前线程的run方法
         * Thread.currentThread().getName() 获取线程名称
         */
//        myThread.run();//切换运行,发现线程的main和Mythread名称都是一致的
        myThread.start();
        /**
         * 同一个对象的线程的start方法不能被多次调用
         * 可以用创建新对象的方式调用start方法
         */
        myThread.setName("我的线程000");
//        myThread.run();//错误


        myThread1.start();
        myThread1.setName("我的线程111");

        //main线程调度
        Thread.currentThread().setName("main线程");

        System.out.println("myThread存活状态:"+myThread.isAlive());
//        myThread1.join();//表示main线程阻塞,其他线程交替执行


        for (Integer i=0;i<100;i++){
            if (i%2==0) System.out.println(i+":"+Thread.currentThread().getName());
        }
    }
}
//1.创建 一个集成于thread类
class MyThread extends Thread{
    public MyThread(String name) {
        super(name);
    }
    public MyThread() {

    }
    /**
     * 2.
     *从写thread类的run方法
     * 根据项目的实际功能编辑
     * 这里做测试 就打印0-100的偶数
     */
    @Override
    public void run() {
        for (Integer i=0;i<100;i++){
//            try {
//                sleep(10);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
            if (i%2==0) System.out.println(i+":"+Thread.currentThread().getName());

        }
    }
}

3.2Runnable方式

1) 定义子类,实现Runnable接口。

2) 子类中重写Runnable接口中的run方法。

3) 通过Thread类含参构造器创建线程对象。

4) 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。

5) 调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法。

代码演示

package com.dfp.dome1;

/**
 * 创建多线程方式2:实现Runnable接口
 * 1.创建一个类实现runnable接口
 * 2.实现run方法
 * 3.创建此类的对象
 * 4.这个类作为参数传递给thread类中的构造器,创建thread类的对象
 * 5.通过thread类的对象调用start方法启动线程
 */
public class RunnableThread {
    public static void main(String[] args) {
        RThread rThread=new RThread();
        Thread thread=new Thread(rThread);
        /**
         * 对线程的操作比如修改属性这些都是通过  thread.的方式来进行
         */
        thread.start();
    }


}
class RThread implements Runnable{

    @Override
    public void run() {
        for (Integer i=0;i<100;i++){
            if (i%2==0) System.out.println(i+":"+Thread.currentThread().getName());
        }
    }
}

3.3 抢票案例演示


/**
 * 创建线程的2种方式的比较
 * 开发中有限选择实现Runnable接口的方式
 *      1.实现的方式没有类的单继承的局限性
 *      2.实现的方式更合适处理多个线程共享数据的情况
 *  相同:
 *      都需要重写run方法
 *      线程的逻辑处理都需要在run方法中实现
 */
public class WindowsDome {
    public static void main(String[] args) {
        DomeRunnable domeRunnable=new DomeRunnable();
        Thread t1=new Thread(domeRunnable);
        Thread t2=new Thread(domeRunnable);
        Thread t3=new Thread(domeRunnable);
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}
class DomeRunnable implements Runnable{
    private Integer tick=100;
    @Override
    public void run() {
        while (true){
            if (tick>0){
                System.out.println(Thread.currentThread()
                        .getName()+"售出车票为:"+tick);
                tick--;
            }else {
                break;
            }
        }
    }
}

结果

我们发现有个问题 票号100的同时被消费了。但是实际情况是不允许的

如果我们让窗口的线程沉睡了,还会出现-1号的票号问题(小概率概率出现)

 4.线程的生命周期

4.1生命周期的转换

 5.线程同步

1. 多线程出现了安全问题

2. 问题的原因: 当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有 执行完,另一个线程参与进来执行。导致共享数据的错误。

3. 解决办法: 对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以 参与执行。

5.1线程同步的方式

5.2 同步锁

  • 同步机制中的锁

在《Thinking in Java》中,是这么说的:对于并发工作,你需要某种方式来防 止两个任务访问相同的资源(其实就是共享资源竞争)。 防止这种冲突的方法 就是当资源被一个任务使用时,在其上加锁。第一个访问某项资源的任务必须 锁定这项资源,使其他任务在其被解锁之前,就无法访问它了,而在其被解锁 之时,另一个任务就可以锁定并使用它了。

synchronized的锁是什么?

 任意对象都可以作为同步锁。所有对象都自动含有单一的锁(监视器)。

 同步方法的锁:静态方法(类名.class)、非静态方法(this)  同步代码块:自己指定,很多时候也是指定为this或类名.class

注意:

 必须确保使用同一个资源的多个线程共用一把锁,这个非常重要,否则就 无法保证共享资源的安全

 一个线程类中的所有静态方法共用同一把锁(类名.class),所有非静态方 法共用同一把锁(this),同步代码块(指定需谨慎) 

5.3 方式一同步代码块演示


/**
 *问题 描述 出票过程中 出现重票/错票等问题
 * 原因:当某个线程执行的过程中。尚未操作完成的时候。其他线程也参与进来操作
 * 解决方式:当线程a操作第一个票号100的时候 ,其他的线程不能参与进来操作票号100
 *         直至a线程完成任务其他线程才可以操作票号。即使a线程阻塞其他线程也是不能参与。
 * java解决线程冲突的方法
 *      方式1:同步代码块
 *          synchronized(监视器){}
 *          1.多线程操作的共享代码(共享数据)视为同步代码
 *          2.共享数据
 *          3.同步监视器,我们俗称锁,任何一个对象都称之为一把锁(多个线程必须共享一把锁)
 *      方式2:同步方法
 */
public class WindowsDome {
    public static void main(String[] args) {
        DomeRunnable domeRunnable=new DomeRunnable();
        Thread t1=new Thread(domeRunnable);
        Thread t2=new Thread(domeRunnable);
        Thread t3=new Thread(domeRunnable);
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}
class DomeRunnable implements Runnable{
    private Integer tick=100;
    Object object=new Object();//用做锁的对象,注意定义的位置保证唯一性
    @Override
    public void run() {
        while (true){
            //设置同步代码块,并且我们的DomeRunnable之创建一次意味着 objec对象也是创建一次。这么说在3个线程中object是唯一的
            synchronized (object){
                if (tick>0){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread()
                            .getName()+"售出车票为:"+tick);
                    tick--;
                }else {
                    break;
                }
            }
        }
    }
}

5.4 同步方法演示

修改如下

 run方法中调用show方法

5.5 继承Thread的方式实现同步

因为通过继承thread的方式我们实现线程,这个方式创建一个线程就会创建3个类对于我的锁的机制而言就会冲突。我们主要修改一下这几个地方

将我们的锁设置为静态数据这样就是唯一的

  

或者在我们的同步方法加上静态 


public class WindowsDomeThread {
    public static void main(String[] args) {
        WindowExThread t1=new WindowExThread();
        WindowExThread t2=new WindowExThread();
        WindowExThread t3=new WindowExThread();
        t1.setName("t1窗口");
        t2.setName("t2窗口");
        t3.setName("t3窗口");
        t1.start();
        t2.start();
        t3.start();
    }
}
class WindowExThread extends Thread{
    private static Integer tick =100;
    private static Object lock=new Object();//静态
    @Override
    public void run() {
        while (true){
//            synchronized (lock){
//
//                if (tick>0){
//                    try {
//                        Thread.sleep(10);
//                    } catch (InterruptedException e) {
//                        e.printStackTrace();
//                    }
//                    System.out.println(Thread.currentThread()
//                            .getName()+"售出车票为:"+tick);
//                    tick--;
//                }else {
//                    break;
//                }
//            }
            show();
        }

    }
    private static synchronized void show(){
        if (tick>0){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread()
                    .getName()+"售出车票为:"+tick);
            tick--;
        }

    }
}

 线程执行同步代码块或同步方法时,程序调用Thread.sleep()、 Thread.yield()方法暂停当前线程的执行 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程 挂起,该线程不会释放锁(同步监视器)。

应尽量避免使用suspend()和resume()来控制线程

5.6 Lock锁实现同步

从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同 步锁对象来实现同步。同步锁使用Lock对象充当。

java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的 工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象 加锁,线程开始访问共享资源之前应先获得Lock对象。

ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和 内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以 显式加锁、释放锁。


import java.util.concurrent.locks.ReentrantLock;

/**
 * jdk5新增方法 lock锁
 * synchronized 和lock
 *      相同:解决线程安全问题
 *      不同:synchronized机制在执行完成代码同步之后,自动释放监听器(同步代码块和同步方法)
 *          lock需要手动 启动同步(lock())和结束同步(unlicok())
 */
public class JDK5Lock {
    public static void main(String[] args) {
        Window domeRunnable=new Window();
        Thread t1=new Thread(domeRunnable);
        Thread t2=new Thread(domeRunnable);
        Thread t3=new Thread(domeRunnable);
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}
class Window implements  Runnable{
    private Integer tick=100;
    private ReentrantLock lock=new ReentrantLock();
    @Override
    public void run() {
        lock.lock();//上锁
        try {
    while (true){
        if (tick>0){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread()
                    .getName()+"售出车票为:"+tick);
            tick--;
        }else {
            break;
        }
    }
        }finally {
            lock.unlock();//解锁
        }
    }
}

6.线程通信

 

 

 代码演示

import javax.swing.*;

/**
 * 线程通信的例子:使用2个线程打印1-100,线程1/2交替打印
 * 涉及到的方法:
 *      wait():一旦执行此方法,当前线程就进入阻塞状态,并且释放同步监听器
 *      notify():一旦执行方法,就会唤醒一个被wait的线程。如果有多个被wait,优先级高的先唤醒
 *      notifyAll():一旦执行唤醒全部被wait的线程
 * 说明:
 *      1.三个方法必须使用在同步代码块中或者同步方法中
 *      2.三个方法的调用者必须是同步代码块或同步方法中的同步监视器
 *      3.3个方法定义在 Object中
 * sleep和wait
 * 不同点:2个方法执行的位置不同,Thread类中声明sleep object中声明wait
 *        调用的要求不同,sleep可以在所以场景下调用,wait必须在同步代码下调用
 *        如果2个方法都使用在同步代码块和方法中,sleep不会释放锁,wait会释放锁
 */
public class correspondence{
    public static void main(String[] args) {
        Number number=new Number();
        Thread t1=new Thread(number);
        Thread t2=new Thread(number);

        t1.setName("线程1");
        t2.setName("线程2");

        t1.start();
        t2.start();

    }
}
 class Number implements Runnable{
    private int number=1;
    @Override
    public void run() {
        while (true){
            synchronized (this){
                this.notify();//唤醒被阻塞的线程,调用者是当前监视器的对象
//                this.notifyAll();
                if (number<=100){
                    System.out.println(Thread.currentThread().getName()+":"+number);
                    number++;
                    try {
                        wait();//让当前线程阻塞
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else {
                    break;
                }
            }
        }
    }
}

案例演示


public class ProductDome {
    public static void main(String[] args) {
        Clerk clerk=new Clerk();
        Product product1=new Product(clerk);
        Product product2=new Product(clerk);
        Consumer consumer=new Consumer(clerk);

        product1.setName("生产者1");
        product2.setName("生产者2");
        consumer.setName("消费者1");

        product1.start();
        product2.start();
        consumer.start();
    }
}
class Clerk {
    //商品类
    private Integer GoodsCount=0;
    //生产产品
    public synchronized void ProduceGoods()  {
        //设置同步方法 防止线程的安全问题
        if (GoodsCount<20){
            GoodsCount++;
            System.out.println(Thread.currentThread().getName()
                +":开始生产第:"+GoodsCount);
//            当数量可加 通知阻塞的消费者线程 唤醒
            notifyAll();
        }else {
            try {
                /**
                 * 当数量为20设置阻塞
                 */
                wait();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
    //消费产品
    public synchronized void ConsumeProduct()  {
        //设置同步方法 防止线程的安全问题
        if (GoodsCount>0){
            System.out.println(Thread.currentThread().getName()
                    +":开始消费第:"+GoodsCount);
            GoodsCount--;
            //当数量可减并且 已经减少了 可以通知阻塞的生产者线程 进行唤醒
            notifyAll();

        }else {
            try {
//                当数量为负数设置阻塞
                wait();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

}
//生产者进程
class Product extends Thread{
    private Clerk clerk;
    public Product(Clerk clerk){
        this.clerk=clerk;
    }
    @Override
    public void run() {
            while (true){
                try {
                    sleep(90);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                    clerk.ProduceGoods();
            }
    }
}
//消费者进程
class Consumer extends Thread{
    private Clerk clerk;
    public Consumer(Clerk clerk){
        this.clerk=clerk;
    }
    @Override
    public void run() {
        while (true){
            try {
                sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.ConsumeProduct();
        }
    }
}

 7.创建线程的其他方式

7.1  实现Callable 接口

实现callable接口通过FutureTask接口实现callbale。

Future接口

 可以对具体Runnable、Callable任务的执行结果进行取消、查询是 否完成、获取结果等。

 FutrueTask是Futrue接口的唯一的实现类

 FutureTask 同时实现了Runnable, Future接口。它既可以作为 Runnable被线程执行,又可以作为Future得到Callable的返回值

代码实现


import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * callable和Runnable比较的优势
 *      1.call可以带返回值
 *      2.call可以抛出异常
 *      3.callable是支持泛型的
 */
public class CreatCallable {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
//        3.创建cabble实现的接口
        CallableThread callableThread=new CallableThread();
//        4.将cabble对象传递给FutureTask创建对象
        FutureTask<Integer> futureTask=new FutureTask<Integer>(callableThread);
//        FutureTask futureTask=new FutureTask<>(callableThread);

//        5.将FutureTask传递给thread对象启动线程
        new Thread(futureTask).start();
        /**
         * get的返回值来自于 FutureTask构造器传递的参数对象Callable类中重写的call方法的返回值
         * 如果需要明确的定义返回值的类型需要在new FutureTask<>中显示的把泛型的类型定义
         * 默认也是Object类型
         */
        Integer sum =  futureTask.get();
        System.out.println("总和为"+sum);
    }
}
//1.创建一个类实现Callable
class CallableThread implements Callable<Integer>{
    Integer sum=0;
   /**2.重写call方法,将线程需要执行的任务或者逻辑代码声明在call中可以带返回值
    *  call方法可以带返回值
    *  call的返回值的类型和实现类中定义的类一致(默认为Object)
    */
    @Override
    public Integer call() throws Exception {
        for (Integer i=0;i<=100;i++){
             if (i%2==0){
                 System.out.println(i);
                 sum+=i;
             }
        }
        return sum;
    }
}

7.2使用线程池

 背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程, 对性能影响很大。

 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完 放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交 通工具。

 好处:

 提高响应速度(减少了创建新线程的时间)

 降低资源消耗(重复利用线程池中线程,不需要每次都创建)

 便于线程管理

 corePoolSize:核心池的大小

 maximumPoolSize:最大线程数

 keepAliveTime:线程没有任务时最多保持多长时间后会终止

创建方式:

 JDK 5.0起提供了线程池相关API:ExecutorService 和 Executors

 ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor

 void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行 Runnable

 Future submit(Callable task):执行任务,有返回值,一般又来执行 Callable

 void shutdown() :关闭连接池

 Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池

 Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池

 Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池

 Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池

 Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运 行命令或者定期地执行。


/**
 * 优势
 * 1.提高响应时间
 * 2.减少资源消耗
 * 3.便于线程管理
 *      setMaximumPoolSize:最大线程数量
 *      setCorePoolSize:核心池大小
 *      setKeepAliveTime:线程没有响应或者没有任务的最大等待时间
 *
 */
public class PoolCreat {
    public static void main(String[] args) {
        ExecutorService service= Executors.newFixedThreadPool(10);

        //通过ExecutorService接口的实现类设置属性
        //获取实现类
        System.out.println(service.getClass());
        ThreadPoolExecutor serviceImpl= (ThreadPoolExecutor) service;
        serviceImpl.setMaximumPoolSize(500);
        serviceImpl.setCorePoolSize(300);
        serviceImpl.setKeepAliveTime(100, TimeUnit.MICROSECONDS);//数值,单位

        //使用于callable接口
        //service.submit();

        //使用于Runnable接口  启动2个不同的线程
        service.execute(new NumThread());
        service.execute(new stringThread());
        //关闭连接池
        service.shutdown();
    }

}
class NumThread implements Runnable{

    @Override
    public void run() {
        for (Integer i= 0;i<=100;i++){
            if (i%2!=0){
                System.out.println(i);
            }
        }
    }
}
class stringThread implements Runnable{

    @Override
    public void run() {
        for (Integer i= 0;i<=100;i++){
            if (i%2!=0){
                System.out.println("aaa");
            }
        }
    }
}

  • 4
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1. 建立三个线程,并且同时运行它们。当运行时输出线程的名称。 实验步骤: (1)、创建类sy6_1 (2)、创建三个线程,调用start()方法启动这三个线程 (3)、保存文件,调试并编译运行程序。 参考程序运行效果: 2. 实现3个类:Storage、Counter和Printer。 Storage类应存储整数。 Counter应创建线程线程从0开始计数(0,1,2,3…)并将每个值存储到Storage类。 Printer类应创建一个线程线程读取Storage类的值并打印值。编写程序创建Storage类的实例,并创建一个Counter对象和Printer对象操作此实例。 实验步骤: (1)、创建三个类Counter, Printer,Storage (2)、创建TestCounter类,在该类定义main函数,在main函数定义Storage对象、Counter对象和 Printer对象,创建Counter线程和Printer线程并启动 (3)、保存文件,调试并编译运行程序。 参考程序运行效果: 3. 修改实验1第2题的程序,添加适当代码,以确保每个数字都恰好只被打印一次。 实验步骤: (1)、创建三个类Counter, Printer,Storage (2)、 创建TestCounter类,在该类定义main函数,在main函数定义Storage对象、Counter1对象和 Printer对象,创建Counter线程和Printer线程并启动 (3)、在定义Storage类的setValue(int i) 和getValue ()方法时使用synchronized关键字,将其定义为同步方法 (4)、保存文件,调试并编译运行程序。 参考程序运行效果:

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值