java基础【多线程】

java基础【多线程】,第1张

java基础【多线程

1、什么是程序、进程、线程

程序:为完成特定任务、用某种语言编写的一组指令的集合,即一段可以执行的静止的代码,保存在硬盘上的一个文件。

进程:正在运行的程序。同一个程序可以启动多个进程。在内存中处于激活状态,有生命周期。

线程:进程可进一步细化分为线程,是一个程序内部的一条执行路径。也称为进程中的子任务。

多线程又称作高并发。抢占式的,谁抢到谁执行。

2、何时需要多线程

程序需要同时执行两个及两个以上的任务。

程序需要实现一些需要等待的任务(用户输入、文件读写,网络 *** 作、搜索)。

需要一些后台运行的程序。

多线程就是把不同方法压入到多个栈区中执行,其中main()为主线程的入口,run()为子线程的入口。

3、创建线程的两种方法

方法一(实现Runnable接口):

        写一个具体类,实现Runnable接口。在类中实现Runnable接口中的run方法,这个run方法就是线程体。

        创建这个类的对象,将对象作为实际参数传递给Thread类中的构造方法,创建Thread线程对象。

        调用Thread线程对象中的start方法,开启线程,调用Runnable接口子类的run方法。

public class HelloThread implements Runnable{

    private int i;

    @Override
    public void run() {
        for(i = 0; i < 100; i++){
            System.out.println(Thread.currentThread().getName() + "子线程:" + i);
        }
    }
}

public class HelloThreadTest {

    public static void main(String[] args){
        Runnable runner = new HelloThread();
        Thread thread = new Thread(runner); //对象关联,创建一个新栈
        thread.start();     //启动子线程,激活栈,并将run方法压入栈,两个栈同步执行

        for(int i = 0; i < 100; i++){
            System.out.println(Thread.currentThread().getName() + "主线程:" + i);
        }
    }
    
}

并发的例子

public class StopRunner implements Runnable{

    private int count = 0;
    private boolean flag = true;

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    public boolean isFlag() {
        return flag;
    }

    @Override
    public void run(){

        while(flag){
            System.out.println(count++);
            if(count == 200){
                count = 0;
            }
        }

        System.out.println("关键代码");
    }
}

public class StopRunnerTest {

    public static void main(String[] args){
        Runnable runner = new StopRunner();
        Thread thread = new Thread(runner);
        thread.start();

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

        }

        ((StopRunner)runner).setFlag(false);    //以通知的方式停止线程

        System.out.println("主线程结束");
    }
}

练习:子线程1随机打印1-100随机整数,子线程二监控键盘,直到输入q为止停止程序,main方法调用这两个子线程。

public class RandomRunnerable implements Runnable{

    private boolean flag = true;

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run(){
        while (flag) {
            System.out.println((int) (Math.random() * 100));
        }
    }
}

public class KeyListener implements Runnable{

    //对象关联
    private Runnable runner;

    public KeyListener(Runnable runner){
        this.runner = runner;
    }

    @Override
    public void run(){
        InputStream in = System.in;
        InputStreamReader inputStreamReader = null;
        BufferedReader bufferedReader = null;

        try {
            inputStreamReader = new InputStreamReader(in);
            bufferedReader = new BufferedReader(inputStreamReader);
            String line = bufferedReader.readLine();
            while(line != null){
                if(line.equalsIgnoreCase("q")){
                    ((RandomRunnerable) runner).setFlag(false);
                    break;
                }

                line = bufferedReader.readLine();
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if(bufferedReader != null){
                try {
                    bufferedReader.close();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }

    }
}

public class RandomRunnerableTest {

    public static void main(String[] args){
        Runnable runner= new RandomRunnerable();
        Thread thread = new Thread(runner);
        thread.start();

        Runnable runner1 = new KeyListener(runner);
        Thread thread1 = new Thread(runner1);
        thread1.start();
    }
}

方法二(继承Thread类)

        写一个类,继承Thread,并重写run方法。此方法就是线程体。

        创建这个类的对象,相当于创建了线程对象。

        调用这个线程对象的start方法。

class MyThread extends Thread{
    @Override
    public void run(){
        for(int i = 0; i < 100; i++){
            System.out.println(currentThread().getName() + i);
        }
    }
}

public class ThreadTest {
    public static void main(String[] args){
        MyThread myThread = new MyThread();
        myThread.start();

        System.out.println("main..");
        for(int i = 0; i < 100; i++){
            System.out.println(myThread.currentThread().getName() + i);
        }
    }
}

两种方法的区别与联系

        实现Runnable:线程代码存放在接口的子类的run方法中。

        继承Thread:线程代码存放Thread子类run方法中。

方法一的好处:

        避免了单继承的局限性。

        多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程处理同一份资源。

4、几个方法

        void join()         谁调用谁执行,其他线程先休息一下。

        static void sleep(long millis)         让当前线程(正在执行此方法的栈的线程)进入睡眠转态一段时间,注意与调用者无关,而是此方法在哪个栈中执行,在哪个栈中执行,哪个栈休眠。

                解除sleep状态两种途径:休眠时间到了或者被别的线程打断(在别的线程中调用此线程对象的interrupt方法),当被别的线程打断会抛出异常。

5、线程同步安全问题

        多个线程执行的不确定性引起执行结果的不确定性(同时对同一个数据的修改)

public class Counter implements Runnable{

    private int counter = 200;
    @Override
    public void run(){

        for(int i = 0; i < 50; i++){
            counter -= 2;
            try {
                Thread.sleep(10);
            }catch (Exception e){
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":" + counter);
        }
    }
}

public class CounterTest {

    public static void main(String[] args){
        Counter counter = new Counter();
        Thread thread = new Thread(counter);
        thread.start();

        Thread thread1 = new Thread(counter);
        thread1.start();
    }
}

两个线程同时访问同一数据造成结果的错误,解决办法,上锁!

public class Counter implements Runnable{

    private int counter = 200;
    @Override
    public void run(){

        for(int i = 0; i < 50; i++){
            synchronized (this){    //()中是一个锁对象,任意对象都可以,称为互斥锁,只允许一个线程进入执行,其他线程等待
                //以下代码具有了原子性,不可分割
                counter -= 2;
                try {
                    Thread.sleep(10);
                }catch (Exception e){
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + ":" + counter);
            }
        }
    }
}

练习:

编写程序,在main方法中创建两个线程。线程1每隔一定时间(200ms以内的随机时间)产生一个0-100之间的随机整数,打印后将该整数放到集合中;
共产生100个整数,全部产生后,睡眠30秒;
在线程2中,唤醒上述睡眠的线程1,并获取线程1中的集合并打印集合内容。

public class ExerRunner implements Runnable {

	// 提醒子线程, 此主存中的属性不要制作副本...
	private volatile boolean over = false;
	private List list = new ArrayList();
	
	public boolean isOver() {
		return over;
	}
	
	public List getList() {
		return list;
	}
	
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			int rand = (int)(Math.random() * 100);
			System.out.println(rand);
			list.add(rand);
			
			int time = (int)(Math.random() * 200);
			try {
				Thread.sleep(time);
			} catch (InterruptedException e) {
				System.out.println("在短睡时被打断, 无所谓");
			}
		}
		over = true;
		
		System.out.println("要睡30秒了");
		try {
			Thread.sleep(30 * 1000);
		} catch (InterruptedException e) {
			System.out.println("在长睡时被打断.... 很生气");
		}
		
		System.out.println("睡起来后 ");
	}

}

public class Observer implements Runnable {
	
	private Runnable runner;
	private Thread thread;
	
	public Observer(Runnable runner, Thread thread) {
		this.runner = runner;
		this.thread = thread;
	}

	@Override
	public void run() {
		// 观察者不断的观察
		while (!((ExerRunner)runner).isOver());
		thread.interrupt();
		
		// 获取集合
		List list = ((ExerRunner)runner).getList();
		for (Integer integer : list) {
			System.out.println("观察者 : " + integer);
		}
	}
	
	
}

public class ExerRunnerTest {
	
	public static void main(String[] args) {
		Runnable runner = new ExerRunner();
		Thread thread = new Thread(runner);
		thread.start();
		
		Runnable runner2 = new Observer(runner, thread); // 对象关联
		Thread thread2 = new Thread(runner2);
		thread2.start();
	}
}

锁对象的wait方法(消费者):使得当前线程挂起并放弃CPU,放弃锁对象,这样别的线程就可以访问并修改共享资源。

锁对象的nofity方法(生产者):唤醒正在排队等待同步资源线程中优先级最高者。

练习:实现线程一、线程二交替打印1-100

class Counter implements Runnable {
	
	private int n = 1;

	@Override
	public void run() {
		for (int i = 0; i < 50; i++) {
			synchronized ("") {
				System.out.println(Thread.currentThread().getName() + " : " + n++);
				"".notify();
				if (i < 49) {
					try {
						"".wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}
	}

}

public class WaitNotifyTest {
	
	public static void main(String[] args) {
		Runnable runner = new Counter();
		
		Thread thread1 = new Thread(runner);
		Thread thread2 = new Thread(runner);
		
		thread1.setName("线程1");
		thread2.setName("线程2");
		
		thread1.start();
		thread2.start();
	}
}

练习:

        银行有一个账户Account包含属性name, balance,写一个普通类Deposit实现Runnable, 在run方法中存钱,有两个柜台(线程)分别同时向同一个账户存3000元,每次存1000,存3次。每次存完打印账户余额。睡眠10毫秒。
        另一个柜台Withdraw取3000元, 每次取1000,取3次。

public class Account {

	private String name;
	private double balance;

	public Account() {
	}

	public Account(String name, double balance) {
		super();
		this.name = name;
		this.balance = balance;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public double getBalance() {
		return balance;
	}

	public void setBalance(double balance) {
		this.balance = balance;
	}

	@Override
	public String toString() {
		return "Account [name=" + name + ", balance=" + balance + "]";
	}

}

public class Deposit implements Runnable {
	
	private Account account;
	
	public Deposit(Account account) {
		super();
		this.account = account;
	}

	@Override
	public void run() {
		//向同一个账户存3000元,每次存1000,存3次。每次存完打印账户余额。睡眠10毫秒
		for (int i = 0; i < 6; i++) {
			synchronized ("") {
				int money = 500;
				account.setBalance(account.getBalance() + money);
				System.out.println(Thread.currentThread().getName() + " 存完钱后 : " + account);
				"".notify();
			}
			
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
		}
	}
}

public class Withdraw implements Runnable {
	
	private Account account;
	
	public Withdraw(Account account) {
		super();
		this.account = account;
	}

	@Override
	public void run() {
		// 取钱
		for (int i = 0; i < 3; i++) {
			synchronized ("") {
				if (account.getBalance() < 1000) {
					System.out.println("钱不够, 进入等待");
					try {
						"".wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				int money = 1000;
				account.setBalance(account.getBalance() - money);
				System.out.println(Thread.currentThread().getName() + " 取钱后 : " + account);
			}
		}
	}

}

public class AccountTest {
	
	public static void main(String[] args) {
		Account account = new Account("张三", 0);
		
		Runnable runner1 = new Deposit(account);
		Runnable runner2 = new Withdraw(account);
		
		Thread thread3 = new Thread(runner2);
		thread3.setName("取钱柜台1");
		thread3.start();
		
		Thread thread1 = new Thread(runner1);
		thread1.setName("存钱柜台1");
		//Thread thread2 = new Thread(runner1);
		//thread2.setName("存钱柜台2");
		
		thread1.start();
		//thread2.start();
		
	}
}

当前锁对象的wait方法可实现释放锁。

死锁:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源。

 死锁的解决办法:不要嵌套锁(synchronized),即使必须要嵌套,保证锁对象尽可能的少。

可重入锁:同一个线程可以无限次获取同一个锁。

本章重点:重原理轻应用

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5709246.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存