多线程的开启方式 线程的方法 同步

本文介绍了Java中多线程的启动方式,包括继承Thread、实现Runnable和Callable接口,以及线程的中断、加入和礼让。讨论了线程同步的重要性,提到了死锁现象和解决方案。此外,还讲解了volatile关键字的作用,以及Timer定时器对象和TimerTask的使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.什么是进程? - 正在执行的程序
2.什么是线程? - 进程的子单位,一个能够完成独立功能的执行路径
3.为什么需要开启多线程?
     a. 当执行某些耗时操作的任务的时候需要开启多线程,防止线程阻塞
     b. 能够让两个任务看起来像是在同时执行
     c. 提高CPU的使用率,进而提高进程和内存的使用率
4.为什么开启多线程会同时执行? - 因为CPU切换执行的速度太快了,肉眼无法差距
5.开启多线程是不是越多越好,提高了效率还是降低了效率? - 不是,线程越多,效率越慢,但是太少,浪费CPU资源,所以,合理利用CPU
6.并发和并行的区别

  •  并发 --> 在同一个时间段下同时执行多个线程,看起来像同时执行
    
  •  并行 --> 在同一个时间刻度下(不能够在分割的时间单位)执行多个线程,本质就上就是同时执行
    
  •  CPU在某一个最小的时间刻度单位下,执行的是一个进程的一个线程的一个不可再分割的原子性语句
    
  •  举例: a++ 是线程安全的吗? 不是  
    

7.同步和异步的区别 – 后期回顾
8.Java虚拟机的启动至少开启了两条线程,主线程和垃圾回收线程
9.一个线程可以理解为进程的子任务
如何来写线程:
任务
任务参数
任务结果

线程的启动方式本质有两种:
1.继承Thread类的方式

2.实现Runnable的方式

  • 方式一:继承Thread类
    1.自定义类MyThread继承Thread类。
    2.MyThread类里面重写run()方法。
    3.创建线程对象。
    4.启动线程。
    注意:
    1、启动线程使用的是start()方法而不是run()方法
    2、线程能不能多次启动
    代码演示使用方式一开启线程

  • 方式二:实现Runnable接口
    1.自定义类MyRunnable实现Runnable接口
    2.重写run()方法
    3.创建MyRunnable类的对象
    4.创建Thread类的对象,并把步骤3创建的对象作为构造参数传递
    启动线程
    实现接口方式的好处
    可以避免由于Java单继承带来的局限性。
    适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效分离,较好的体现了面向对象的设计思想。

多线程的实现方式三: 实现Callable方式开启线程

  • 继承Thread和实现Runnable的方式的特点:
    1.没有返回结果
    2.没有异常

  • Callable和Runnable的区别:
    1.Runnable无返回值,没有异常抛出的
    2.Callable可以在启动线程中获取返回值,以及接受子线程的异常

  • 线程间的数据传递:线程通信
    A线程中开启了B线程
    A --> B 通过构造方法
    B --> A 通过Callable方式

匿名内部类的方式开启线程

  • 注意: 当继承Thread和实现Runnable方式同时实现,继承Thread优先
public class ThreadDemo04 {
	public static void main(String[] args) {
		new Thread(); // 匿名线程对象
		
		// 方式一继承Thread方式开启线程
		new Thread() {
			public void run() {
				for (int i = 0; i < 100; i++) {
					System.out.println("A.继承Thread方式:" + i);
				}
			}
		}.start();
		
		// 方式二实现Runnable接口的方式开启线程
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				for (int i = 0; i < 100; i++) {
					System.out.println("B.实现Runnable接口的方式:" + i);
				}
			}
		}).start();
		
		
		for (int i = 0; i < 100; i++) {
			System.out.println("C.主线程:" + i);
		}
		
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				for (int i = 0; i < 100; i++) {
					System.out.println("D.实现Runnable接口的方式:" + i);
				}
			}
		}) {
			public void run() {
				for (int i = 0; i < 100; i++) {
					System.out.println("E.继承Thread方式:" + i);
				}
			}
		}.start();
	}
}

Lambda表达式开启线程

什么是Lambda表达式 一种函数式接口的新的写法,本质还是匿名内部类,但是这个父类是函数式接口

  • 什么是函数式接口?
    – 只有一个抽象方法的接口称为函数式接口
    Runnable, FileFilter

  • Lambda表达式的语法:
    () -> {}
    () 小括号里面是参数列表,如果一个函数式接口中的抽象方法没有参数,这里可以不写参数
    -> 固定格式
    {} 重写函数式接口的抽象方法的方法体
    如果方法体中只有一条语句,{} 可以省略不写,如果返回值只有一条语句, return关键字可以省略

public class ThreadDemo05 {
	public static void main(String[] args) {
		new Demo().method(new ITest() {
			
			@Override
			public void show() {
				System.out.println("show");
			}
		});
		
		new Demo().method(()->System.out.println("Lambda表达式的 show"));
		
		new Demo().method((a, b)->System.out.println(a + "|" + b));
		
		new Demo().method((a, b)->a + b);
		
		// com.sxt.threaddemo
		File f = new File("src/com/sxt/threaddemo");
		/*File[] files = f.listFiles(new FileFilter() {
			
			@Override
			public boolean accept(File f) {
				return f.isFile() && f.getName().endsWith(".java");
			}
		});*/
		
		/*File[] files = f.listFiles((file) -> file.isFile() && file.getName().endsWith(".java"));
		
		for (File file : files) {
			System.out.println(file);
		}*/
		ArrayList<String> list = new ArrayList<>();
		list.add("张三丰");
		list.add("李四");
		list.add("赵六");
		list.add("王五哈哈哈");
		
		list.forEach((t)->{
			System.out.println(t);
		});
		
		list.forEach(new Consumer<String>() {

			@Override
			public void accept(String t) {
				System.out.println(t);
			}
		});
		
		list.removeIf((t)-> t.length() == 2);
		System.out.println(list);
		
		/*
		 * new Consumer<String>() {

			@Override
			public void accept(String t) {
				System.out.println(t);
			}
		}
		
		Consumer<? super E> action = new Consumer<String>() {

			@Override
			public void accept(String t) {
				System.out.println(t);
			}
		}
		
		public void forEach(Consumer<? super E> action) {
	        Objects.requireNonNull(action);
	        final int expectedModCount = modCount;
	        @SuppressWarnings("unchecked")
	        final E[] elementData = (E[]) this.elementData;
	        final int size = this.size;
	        for (int i=0; modCount == expectedModCount && i < size; i++) {
	            action.accept(elementData[i]);
	        }
	        if (modCount != expectedModCount) {
	            throw new ConcurrentModificationException();
	        }
	    }
		 */
		
		new Thread(()->{
//			System.out.println("Lambda表达式开启线程");
			for (int i = 0; i < 10000; i++) {
				System.out.println("Lambda:" + i);
			}
		}).start();
		
		for (int i = 0; i < 10000; i++) {
			System.out.println("main:" + i);
		}
	}
}

@FunctionalInterface
interface ITest {
	void show();
//	void test();
}

@FunctionalInterface
interface IDemo {
	void show(int a, String b);
//	void test();
}

@FunctionalInterface
interface IShow {
	int add(int a, int b);
//	void test();
}
class Demo {
	/*
	 * ITest test = new ITest() {
			
			@Override
			public void show() {
				System.out.println("show");
			}
		};
	 */
	public void method(ITest test) {
		test.show();	
	}
	
	public void method(IDemo d) {
		d.show(10, "sss");	
	}
	
	public void method(IShow s) {
		int add = s.add(100, 200);
		System.out.println(add);
	}
}

设置和获取线程名称的几种方式
1.通过构造方法
2.通过set/get方法
3.通过静态方法

public class ThreadDemo01 {
	public static void main(String[] args) {
		/*MyThread t = new MyThread();
		t.start();
		
		MyThread t2 = new MyThread();
		t2.start();*/
		
		/*MyThread t3 = new MyThread("隔壁老王");
		t3.start();*/
		/*Thread t3 = new Thread("隔壁老王");
		t3.start();*/
		
		/*MyThread t3 = new MyThread();
		t3.setName("隔壁老王家");
		t3.start();*/
		
		/*Thread.currentThread().setName("主线程");
		String name = Thread.currentThread().getName();
		System.out.println(name);*/
		
		Thread t4 = new Thread(new MyRunnable(), "隔壁老李");
		t4.start();
		
		/*long mainId = Thread.currentThread().getId();
		System.out.println(mainId);*/
	}
}

class MyThread extends Thread {
	/*public MyThread() {
		super();
	}
	
	public MyThread(String name) {
		super(name);
	}*/
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println(this.getName() + ":" + i);
		}
	}
}

class MyRunnable implements Runnable {

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

中断线程
public final void stop()
public void interrupt()
面试题: stop和interrupt的区别
stop方法表示结束线程的生命
interrupt表示向线程抛出一个InterruptedException异常
public class ThreadDemo04 {
	public static void main(String[] args) {
		
		Thread t = new Thread(new StopThread());
		t.start();
		System.out.println("主线程: 嘘,开始数,准备好");
		try {
			Thread.sleep(4000);
			// t.stop();
			t.interrupt();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

class StopThread implements Runnable {

	@Override
	public void run() {
		String startTime = new SimpleDateFormat("开睡时间: yyyy-MM-dd HH:mm:ss").format(new Date());
		System.out.println(startTime);
		
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			e.printStackTrace();
			System.out.println("谁打我!!!");
			System.out.println("打扰了,我自己来");
			System.exit(0);
		}
		
		String endTime = new SimpleDateFormat("觉醒时间: yyyy-MM-dd HH:mm:ss").format(new Date());
		System.out.println(endTime);
	}
	
}

后台线程
public final void setDaemon(boolean on)

一般来说,JVM(JAVA虚拟机)中一般会包括俩种线程
线程分类
	a.用户线程
	b.后台线程 、守护线程、服务线程

        所谓后台线程(daemon)线程指的是:在程序运行的时候在后台提供的一种通用的服务的线程,并且这种线程并不属于程序中不可或缺的部分。
         因此,当所有的非后台线程结束的时候,也就是用户线程都结束的时候,程序也就终止了。同时,会杀死进程中的所有的后台线程。
         反过来说,只要有任何非后台线程还在运行,程序就不会结束。比如执行main()的就是一个非后台线程。
        基于这个特点,当虚拟机中的用户线程全部退出运行时,守护线程没有服务的对象后,JVM也就退出了。

线程加入

public final void join()

public class ThreadDemo06 {
	public static void main(String[] args) {
		JoinThread t1 = new JoinThread();
		JoinThread t2 = new JoinThread();
		JoinThread t3 = new JoinThread();
		
		t1.setName("刘备");
		t2.setName("关羽");
		t3.setName("张飞");
		
		t1.start();
		try {
			t1.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		t2.start();
		try {
			t2.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		t3.start();
		
		
	}
}

class JoinThread extends Thread {
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println(getName() + ":" + i);
		}
	}
}
线程礼让

public static void yield()

  • 让正在执行的线程让出CPU的执行权一小会
  • t1和t2互相争夺CPU的执行权,t1抢到了CPU的执行权,让出CPU的执行权,重新回到线程队列中继续抢夺CPU的执行权
public class ThreadDemo07 {
	public static void main(String[] args) {
		YieldThread t1 = new YieldThread();
		YieldThread t2 = new YieldThread();
		
		t1.setName("孔融");
		t2.setName("孔融的哥哥");
		
		t1.start();
		t2.start();
	}
}

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

死锁:指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象。
代码演示死锁现象。
public class DeadThradDemo {
	public static void main(String[] args) {
		DieLock dl1 = new DieLock(true);
		DieLock dl2 = new DieLock(false);

		dl1.start();
		dl2.start();
	}
}

class DieLock extends Thread {
	private boolean flag;

	public DieLock() {
	}

	public DieLock(boolean flag) {
		this.flag = flag;
	}

	@Override
	public void run() {
		if (flag) {
			// dl1进来
			synchronized (TestLock.LOCKA) {
				System.out.println("if 语句中 LockA锁"); // 就在输出完这句话之后被dl2抢到了资源
				synchronized (TestLock.LOCKB) {
					System.out.println("if 语句中 LockB锁");
				}
			}
		} else {
			// dl走else
			synchronized (TestLock.LOCKB) {
				System.out.println("else 语句中 lockB锁");
				synchronized (TestLock.LOCKA) {
					System.out.println("else 语句中 lockA锁");
				}
			}
		}
	}
}

class TestLock {
	public static final Object LOCKA = new Object();
	public static final Object LOCKB = new Object();
}

需求:深圳罗湖火车站目前正在出售车票,共有100张票,而它有3个售票窗口售票,

  • 请设计一个程序模拟该火车站售票。

  • 要求:使用两种方式实现
    继承Thread类
    实现Runnable接口

继承Thread和实现Runnable的方式的区别
1.继承Thread可以直接使用 Thread里面的方式
2.实现接口方式的好处
a.可以避免由于Java单继承带来的局限性。
b.适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效分离,较好的体现了面向对象的设计思想。

出现的问题
1.卖出了同票
窗口A正在出售第97张票
窗口B正在出售第97张票
窗口C正在出售第97张票

2.卖出了负票
窗口A正在出售第1张票
窗口C正在出售第0张票
窗口B正在出售第-1张票

问题产生的原因: CPU在某一个最小的时间刻度单位下,执行的是一个进程的一个线程的一个不可再分割的原子性语句

解决办法:
1.同步代码块
2.同步方法
3.同步锁

可能出现线程安全的问题的情况:
	1.存在多线程环境
	2.多个线程共享同一份数据
	3.多个线程操作同一份数据并且共享数据做了修改
	4.存在多条语句操作共享数据
	
	在多线程环境下,存在多条语句操作共享数据,并且对数据做了修改的操作,那么必定会出现线程安全问题
	
解决办法: 将多条操作共享数据的语句 包裹起来,同步锁
public class ThreadSynchronizationDemo {
	public static void main(String[] args) {
	/*	SellTicketThread t1 = new SellTicketThread();
		SellTicketThread t2 = new SellTicketThread();
		SellTicketThread t3 = new SellTicketThread();*/
		SellTicketThread st = new SellTicketThread();
		Thread t1 = new Thread(st);
		Thread t2 = new Thread(st);
		Thread t3 = new Thread(st);
		
		t1.setName("窗口A");
		t2.setName("窗口B");
		t3.setName("窗口C");
		
		t1.start();
		t2.start();
		t3.start();
		
	}
}

/*
 * 解决办法方式一:同步代码块
	格式:
		synchronized(对象){需要同步的代码;}
	同步的好处
	解决了多线程的安全问题。
	同步的弊端
	当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,降低程序的运行效率。如果出现了同步嵌套,就容易产生死锁问题
	注意: 这里的对象是锁对象,可以是任意对象,但是必须多个线程共享同一个对象
		 这种方式加锁的时间是进入代码之后,释放锁的时间是代码块执行结束的时候

 */
class SellTicketThread implements Runnable {
	private int tickets = 100;
	@Override
	public void run() {
		while (true) {
			synchronized (MyLock.LOCK) {
				if (tickets > 0) {
					try {
						Thread.sleep(100L);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
				} 
			}
			
		}
	}
}

enum MyLock {
	LOCK
}

/*
 * 解决办法方式二:同步方法
	格式:
		public synchronized 返回值 方法名(参数列表) {
	      		//需要同步的代码块
	   	}
	
	如果锁对象是this,就可以考虑使用同步方法。
	
	如果方式静态方法, 当前类对应的字节码文件对象作锁 Class c = SellTicketThread.class
 */
/*class SellTicketThread implements Runnable {
	private static int tickets = 100;

	@Override
	public void run() {
		while (true) {
			sellTicket();
		}
	}
	
	public static synchronized void sellTicket() { 
		if (tickets > 0) {
			try {
				Thread.sleep(100L);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
		}
	}
}*/

/*class SellTicketThread extends Thread {
	
	private static int tickets = 100;
	
	@Override
	public void run() {
		while (true) {
			sellTicket();
		}
	}
	
	public static synchronized void sellTicket() {
		if (tickets > 0) {
			try {
				Thread.sleep(100L);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
		}
	}
}*/

/*class SellTicketThread implements Runnable {
	private static int tickets = 100;
	Lock lock = new ReentrantLock();
	@Override
	public void run() {
		while (true) {
			lock.lock();
			if (tickets > 0) {
				try {
					Thread.sleep(100L);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
			}
			lock.unlock();
		}
	}
}
*/

线程池和线程组的区别

线程组为了方便管理线程,可以将线程分组
ThreadGroup
主线程的组名叫做 main

public class ThreadGroupDemo {
	public static void main(String[] args) {
		ThreadGroup tg = Thread.currentThread().getThreadGroup();
		System.out.println(tg.getName());
		
		ThreadGroupRunnable tr = new ThreadGroupRunnable();
		
		ThreadGroup sgyy = new ThreadGroup("三国演义组");
		// 第一组 三国演义组
		Thread t1 = new Thread(sgyy, tr, "诸葛亮");
		Thread t2 = new Thread(sgyy, tr, "司马懿");
		Thread t3 = new Thread(sgyy, tr, "周瑜");
		
		ThreadGroup shz = new ThreadGroup("水浒传");
		// 第一组 三国演义组
		Thread t4 = new Thread(shz, tr, "李逵");
		Thread t5 = new Thread(shz, tr, "宋江");
		Thread t6 = new Thread(shz, tr, "卢俊义");
		
		/*t1.start();
		t2.start();
		t3.start();*/
		List<Thread> threadList = new ArrayList<Thread>();
		threadList.add(t1);
		threadList.add(t2);
		threadList.add(t3);
		
		// 批量设置为后台线程
		sgyy.setDaemon(true);
		
		sgyy.stop();
		
		for (Thread thread : threadList) {
			thread.start();
		}
		
		System.out.println(t5.getThreadGroup().getName());
	}
}

class ThreadGroupRunnable implements Runnable {

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

Executors工厂类来产生线程池。
构造方法
public static ExecutorService newCachedThreadPool()
public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newSingleThreadExecutor()

public class ThreadPoolDemo {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		ExecutorService pool = Executors.newFixedThreadPool(3);
		pool.submit(new MyRunnable());
		Future<Integer> future = pool.submit(new MyCallable(1, 100));
		pool.submit(new Runnable() {
			
			@Override
			public void run() {
				for (int i = 0; i < 100; i++) {
					System.out.println(Thread.currentThread().getName() + ":" + i);
				}
			}
		});
		Integer i = future.get();
		System.out.println("返回的结果: " + i);
		
		pool.shutdown();
	}
}

class MyRunnable implements Runnable {

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

class MyCallable implements Callable<Integer> {
	
	int m;
	int n;

	public MyCallable(int m, int n) {
		super();
		this.m = m;
		this.n = n;
	}

	public MyCallable() {
		super();
	}

	@Override
	public Integer call() throws Exception {
		int sum = 0;
		for (int i = m; i <= n; i++) {
			System.out.println(Thread.currentThread().getName() + ":" + i);
			sum += i;
		}
		return sum;
	}
	
}
volatile关键字

原因分析:
现在有两个线程,一个是main线程,另一个是RunThread。
它们都试图修改 第三行的 isRunning变量。
按照JVM内存模型,main线程将isRunning读取到本地线程内存空间,修改后,再刷新回主内存。
线程会一直在私有堆栈中读取isRunning变量。
因此,RunThread线程无法读到main线程改变的isRunning变量
从而出现了死循环,导致RunThread无法终止。
解决方法,在第三行代码处用 volatile 关键字修饰即可。
这里,它强制线程从主内存中取 volatile修饰的变量。

总结: volatile关键字的作用是:使变量在多个线程间可见(可见性)
Timer定时器对象和TimerTask 定时器任务
public class TimerTest {
	public static void main(String[] args) {
		Timer t = new Timer();
		TimerTask task = new MyTask(t);
		Calendar c = Calendar.getInstance();
		c.set(2019, 7, 24, 15, 24, 30);
		long time = c.getTimeInMillis();
		Date d = new Date(time);
		// 规定制定的时间启动线程
		// t.schedule(task, d);
		
		// t.schedule(task, 3000L);
		
		t.schedule(task, 3000, 1000);
	}
}

class MyTask extends TimerTask {
	
	private Timer t;
	
	public MyTask(Timer t) {
		super();
		this.t = t;
	}

	public MyTask() {
		super();
	}

	@Override
	public void run() {
		System.out.println("Boom!!!");
		// t.cancel();
	}
	
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值