java_线程、同步、线程池

线程

Java使用 java.lang.Thread 类代表线程,所有的线程对象都必须是Thread类或其子类的实例

Thread类常用方法

构造方法

  • public Thread():分配一个新的线程对象。
  • public Thread(String name):分配一个指定名字的新的线程对象。
  • public Thread(Runnable target):分配一个带有指定目标新的线程对象。
  • public Thread(Runnable target,String name):分配一个带有指定目标新的线程对象并指定名字。

常用方法

  • public string getName():获取当前线程名称。
  • public void start():导致此线程开始执行;Java虚拟机调用此线程的run方法。
  • public void run():此线程要执行的任务在此处定义代码。
  • public static void sleep(long millis):使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
  • public static Thread currentThread():返回对当前正在执行的线程对象的引用。
创建线程方式一

Java中通过继承Thread类来创建并启动多线程的步骤如下:

  1. 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把
    run()方法称为线程执行体。
  2. 创建Thread子类的实例,即创建了线程对象
  3. 调用线程对象的start()方法来启动该线程

测试类:

public class Demo{
	public static void main(String[] args) {
		// 创建自定义线程对象
		MyThread mt = new MyThread("新线程");
		// 开启新线程
		mt.start();
		// 在主方法中执行for循环
		for (int i = 0; i < 10; i++) {
			System.out.println("main线程正在执行" + i);
		}
	}
}

自定义线程类:

public class MyThread extends Thread {
	// 定义指定线程名称的构造方法
	public MyThread(String name) {
		// 调用父类的String参数的构造方法,指定线程的名称
		super(name);
	}
	/**
	 * 重写run方法,完成该线程执行的逻辑
	 */
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println(getName() + "正在执行" + i);
		}
	}
}

流程图:

程序启动运行main时候,java虚拟机启动一个进程,主线程main在main()调用时候被创建。随着调用mt的对象的start方法,另外一个新的线程也启动了,这样,整个应用就在多线程下运行。

创建线程方式二

Java中通过实现Runnable接口来创建并启动多线程的步骤如下:

  1. 定义Runnable接口的实现类,并重写该接口的run () 方法,该run () 方法的方法体同样是该线程的线程执行体。
  2. 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
  3. 调用线程对象的start () 方法来启动线程。

测试类:

public class Demo {
	public static void main(String[] args) {
		// 创建自定义类对象线程任务对象
		MyRunnable mr = new MyRunnable();
		// 创建线程对象
		Thread t = new Thread(mr, "新线程");
		t.start();
		for (int i = 0; i < 10; i++) {
			System.out.println("main线程正在执行" + i);
		}
	}
}

自定义线程类:

public class MyRunnable implements Runnable {
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName() + "正在执行" + i);
		}
	}
}

通过实现Runnable接口,使得该类有了多线程类的特征。run () 方法是多线程程序的一个执行目标。所有的多线程代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。

Thread和Runnable的区别

如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。

实现Runnable接口比继承Thread类所具有的优势:

  1. 适合多个相同的程序代码的线程去共享同一个资源。
  2. 可以避免java中的单继承的局限性。
  3. 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和数据独立。
  4. 线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类。
匿名内部类方式实现线程的创建
public class Demo {
	public static void main(String[] args) {
		//使用匿名内部类方法;直接创建Thread类的子类对象
		/*
		 * new Thread() { public void run() { for (int i = 0; i < 10; i++) {
		 * System.out.println("新线程正在执行" + i); } } }.start();
		 */
		//使用匿名内部类方式;直接创建Runnable接口实现类对象
		Runnable r = new Runnable() {
			public void run() {
				for (int i = 0; i < 10; i++) {
					System.out.println("新线程正在执行" + i);
				}
			}
		};
		new Thread(r).start();
		for (int i = 0; i < 10; i++) {
			System.out.println("main线程正在执行" + i);
		}
	}
}

线程安全

两个或两个以上的线程在访问共享资源时,仍然能得到正确的结果则称之为线程安全

模拟卖50张电影票

public class Ticket implements Runnable {
	private int ticket = 50;
	// 买票操作
	@Override
	public void run() {
		// 每个窗口买票操作,窗口永远开启
		while (true) {
			if (ticket > 0) {
				// 使用sleep方法模拟买票
				try {
					Thread.sleep(1);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				// 获取当前对象的名字
				String name = Thread.currentThread().getName();
				System.out.println(name + "正在卖票:" + ticket--);
			}
		}
	}
}

测试类:

public class Demo {
	public static void main(String[] args) {
		// 创建线程任务对象
		Ticket ticket = new Ticket();
		// 创建三个窗口卖票
		Thread t1 = new Thread(ticket, "窗口一");
		Thread t2 = new Thread(ticket, "窗口二");
		Thread t3 = new Thread(ticket, "窗口三");

		// 同时开始卖票
		t1.start();
		t2.start();
		t3.start();
	}
}

结果出现了这种现象:

这种问题,几个窗口(线程)票数不同步了,称为线程不安全

线程同步

当我们使用多个线程访问统一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题.

要解决上述多线程并发访问多一个资源的安全性问题,java中提供了同步机制(synchronized)来解决,有三种方式完成同步操作:

  1. 同步代码块
  2. 同步方法
  3. 锁机制

同步代码块

同步代码块:synchronized关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问

synchronized(同步锁){
	需要同步操作的代码
}

同步锁注意事项
1.锁对象可以是任意类型。
2.多个线程对象要使用同一把锁。

同步代码块实现线程安全

public class Ticket implements Runnable {
	private int ticket = 50;
	Object lock = new Object();

	// 买票操作
	@Override
	public void run() {
		// 每个窗口买票操作,窗口永远开启
		while (true) {
			synchronized (lock) {
				if (ticket > 0) {
					// 使用sleep方法模拟买票
					try {
						Thread.sleep(1);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					// 获取当前对象的名字
					String name = Thread.currentThread().getName();
					System.out.println(name + "正在卖票:" + ticket--);
				}
			}
		}
	}
}

同步方法

同步方法:使用synchronized修饰的方法,就叫做同步方法保证A线程执行该方法的时候,其他线程只能在方法外等着

public synchronized void method(){
	可能会产生线程安全问题的代码
}

同步方法实现线程安全

public class Ticket implements Runnable {
	private int ticket = 50;
	// 买票操作
	@Override
	public void run() {
		// 每个窗口买票操作,窗口永远开启
		while (true) {
			sellTicket();
		}
	}
	// 锁对象是谁调用这个方法就是谁,this
	public synchronized void sellTicket() {
		if (ticket > 0) {
			// 使用sleep方法模拟买票
			try {
				Thread.sleep(1);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			// 获取当前对象的名字
			String name = Thread.currentThread().getName();
			System.out.println(name + "正在卖票:" + ticket--);
		}
	}
}

Lock锁

java.util.concurrent.locks.Lock 机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。
Lock常用方法

  • public void lock():加同步锁。
  • public void unlock():释放同步锁。

Lock锁实现线程安全

public class Ticket implements Runnable {
	private int ticket = 50;
	Lock lock = new ReentrantLock();
	// 买票操作
	@Override
	public void run() {
		// 每个窗口买票操作,窗口永远开启
		while (true) {
			lock.lock();
			if (ticket > 0) {
				// 使用sleep方法模拟买票
				try {
					Thread.sleep(1);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				// 获取当前对象的名字
				String name = Thread.currentThread().getName();
				System.out.println(name + "正在卖票:" + ticket--);
			}
			lock.unlock();
		}
	}

}

线程池

线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。


线程池能够带来三个好处:

  1. 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
  2. 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
  3. 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存

线程池的使用

Java里面线程池的顶级接口是java.util.concurrent.Executors

public static ExecutorService newFixedThreadPool(int nThreads) :返回线程池对象。(创建的是有界线程池,也就是池中的线程个数可以指定最大数量)

public Future<?> submit(Runnable task) :获取线程池中的某一个线程对象,并执行

Runnable实现类代码:

public class MyRunnable implements Runnable {
	@Override
	public void run() {
		System.out.println("我要一个教练");
	try {
		Thread.sleep(2000);
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
	System.out.println("教练来了: " + Thread.currentThread().getName());
	System.out.println("教我游泳,交完后,教练回到了游泳池");
	}
}

线程池测试类:

public class Demo{
	public static void main(String[] args) {
		// 创建线程池对象
		ExecutorService service = Executors.newFixedThreadPool(2);//包含2个线程对象
		// 创建Runnable实例对象
		MyRunnable r = new MyRunnable();
		//自己创建线程对象的方式
		// Thread t = new Thread(r);
		// t.start(); ---> 调用MyRunnable中的run()
		// 从线程池中获取线程对象,然后调用MyRunnable中的run()
		service.submit(r);
		// 再获取个线程对象,调用MyRunnable中的run()
		service.submit(r);
		service.submit(r);
		// 注意:submit方法调用结束后,程序并不终止,是因为线程池控制了线程的关闭。
		// 将使用完的线程又归还到了线程池中
		// 关闭线程池
		//service.shutdown();
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值