进程:
正在运行的程序,是系统进行资源分配和调用的独立单位;
每一个进程都有他自己的内存空间和系统资源。
线程:(线程是依赖于进程而存在;线程是程序的执行单元;)
是进程的单个顺序控制流,是一条执行路径;
一个进程如果只有一条执行路径,则称为单线程;
一个进程如果有多条执行路径,则称为多线程。
多线程:
优点:
1)适当的提高程序的执行效率(多个线程同时执行)。
2)适当的提高了资源利用率(CPU、内存等)。
缺点:
1)占用一定的内存空间。
2)线程越多CPU的调度开销越大。
3)程序的复杂度会上升。
并行:逻辑上同时发生,指在某一个时间内同时运行多个程序。
并发:物理上同时发生,指在某一个时间点同时运行多个程序。
线程调度:
分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片。
抢占式调度模型:优先让优先权高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的CPU时间片相对多一些。
(一般采用的是抢占式优先)
线程默认的优先级是5;
线程的优先级的范围是:1-10;
● 线程
创建线程的两种方式:
一、继承Thread类,扩展线程。
class DemoThread extends Thread {
@Override
public void run() {
super.run();
// Perform time-consuming operation...
}
}
DemoThread t = new DemoThread();
t.start();
- 继承Thread类,覆盖run()方法。
- 创建线程对象并用start()方法启动线程。
- 3)Thread 类中的 start() 和 run() 方法有什么区别?
-
调用 start() 方法才会启动新线程;如果直接调用 Thread 的 run() 方法,它的行为就会和普通的方法一样;为了在新的线程中执行我们的代码,必须使用 Thread.start() 方法。
区别start()方法和run()方法:public class Test { public static void main(String[] args) { MyThread thread = new MyThread(); thread.start(); } } class MyThread extends Thread{ private static int num = 0; public MyThread(){ num++; } @Override public void run() { System.out.println("主动创建的第"+num+"个线程"); } }
public class Test {
public static void main(String[] args) {
System.out.println("主线程ID:"+Thread.currentThread().getId());
MyThread thread1 = new MyThread("thread1");
thread1.start();
MyThread thread2 = new MyThread("thread2");
thread2.run();
}
}
class MyThread extends Thread{
private String name;
public MyThread(String name){
this.name = name;
}
@Override
public void run() {
System.out.println("name:"+name+" 子线程ID:"+Thread.currentThread().getId());
}
}
- 二、实现 Runnable 接口。
在Java中创建线程除了继承Thread类之外,还可以通过实现Runnable接口来实现类似的功能。实现Runnable接口必须重写其run方法。
public class Test {
public static void main(String[] args) {
System.out.println("主线程ID:"+Thread.currentThread().getId());
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
}
}
class MyRunnable implements Runnable{
public MyRunnable() {
}
@Override
public void run() {
System.out.println("子线程ID:"+Thread.currentThread().getId());
}
}
Runnable的中文意思是“任务”,顾名思义,通过实现Runnable接口,我们定义了一个子任务,然后将子任务交由Thread去执行。注意,这种方式必须将Runnable作为Thread类的参数,然后通过Thread的start方法来创建一个新线程来执行该子任务。如果调用Runnable的run方法的话,是不会创建新线程的,这根普通的方法调用没有任何区别。
事实上,查看Thread类的实现源代码会发现Thread类是实现了Runnable接口的。
在Java中,这2种方式都可以用来创建线程去执行子任务,具体选择哪一种方式要看自己的需求。直接继承Thread类的话,可能比实现Runnable接口看起来更加简洁,但是由于Java只允许单继承,所以如果自定义类需要继承其他类,则只能选择实现Runnable接口。
线程休眠:public static void sleep(long millis)
线程加入:public final void join()
线程礼让:public static void yield()
后台线程:public final void setDaemon(boolean on)
线程中断:
public final void stop()
public void interrupt()
不安全(stop)
线程的生命周期:
线程中,要设置锁,但是要避免死锁的问题
实现100张票三个窗口同时卖票
public class SellTicket implements Runnable{
//定义100张票
private int tickets = 100;
//创建锁对象
private Object obj = new Object();
public void run(){
private synchronized void SellTicket{
while(true) {
if(tickets>1){
try{
Thread.sleep(100);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售"+(tickets--)+"张票");
}
}
} }
public static void main(String[] args){
//创建资源对象SellTicket st = new SellTicket();
//创建3个线程对象
Thread t1=new Thread(st,"窗口1");
Thread t2=new Thread(st,"窗口2");
Thread t3=new Thread(st,"窗口3");
//启动线程t1.start();t2.start();t3.start();}}
方法二:将synchronize写在方法体上
private synchronized void SellTicket{
while(true) {
if(tickets>1){
try{
Thread.sleep(100);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售"+(tickets--)+"张票");
}
}
} }
解决线程安全问题的基本思想:是否是多线程环境;是否有共享数据;是否有多条语句操作共享数据;解决线程安全问题:synchronized(同步代码块):同步对象可以解决安全问题的根本原因就在对象上,该对象如同锁的功能。
public class ThreadDemo {
public static void main(String[] args) {
// 线程安全的类
StringBuffer sb=new StringBuffer();
Vector<String> v=new Vector<String>();
Hashtable<String,String> h=new Hashtable<String,String>();
//
List<String> list1=new ArrayList<String>(); //线程不安全
List<String> list2=Collections.synchronizedList(new ArrayList<String>());//线程安全
}
}
public class ThreadDemo {
public static void main(String[] args) {
// 线程安全的类
StringBuffer sb=new StringBuffer();
Vector<String> v=new Vector<String>();
Hashtable<String,String> h=new Hashtable<String,String>();
//
List<String> list1=new ArrayList<String>(); //线程不安全
List<String> list2=Collections.synchronizedList(new ArrayList<String>());//线程安全
}
}
同步的前提:
多个线程;
多个线程使用的是同一个锁对象;
同步的好处:
同步的出现解决了多线程的安全问题。
同步的弊端:
当线程相当多的时候,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中或降低程序的运行效率。
死锁问题:
死锁是指在两个或两个以上的线程在执行过程中,因争夺资源产生的一种互相等待现象。
Lock锁的使用:
void lock(); //获取锁
void unlock(); //释放锁
//定义100张票
private int tickets = 100;
//定义锁对象
private Lock lock = new ReentrantLock();
public void run(){
while(true) {
try{
//加锁
lock.lock();
if(tickets>1){
try{
Thread.sleep(100);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售"+(tickets--)+"张票");
}
}finally{
//释放锁
lock.unlock();
}
}
}
}
采用Runnable接口方式实现
public class Sellticket implements Runnable {
//定义一百张票
private int tickets = 100;
@Override
public void run() {
while (true){
if (tickets > 0 ){
System.out.println(Thread.currentThread().getName() + "正在出售" + (tickets--) +"张票");
}
}
}
}
public class RunnableDemo {
public static void main(String[] args){
/*
多线程Runnable接口方式卖电影票
*/
//创建资源对象
Sellticket st = new Sellticket();
//创建三个线程对象
Thread t1 = new Thread(st,"窗口1");
Thread t2 = new Thread(st,"窗口2");
Thread t3 = new Thread(st,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
采用Runnable接口方式实现,但是每次卖票延迟100毫秒
改进后的电影院售票出现问题:
CPU的一次操作必须是原子性的
2.还出现了负数的票
随机性和延迟导致的
首先想为什么出现问题?(也是我们判断是否有问题的标准)
是否是多线程环境
是否有共享数据
是否有多条语句操作共享数据
如何解决多线程安全问题呢?
基本思想:让程序没有安全问题的环境。
怎么实现呢?
把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可。
解决线程安全问题实现1:
同步代码块
格式:
synchronized(对象){需要同步的代码;}
同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。(多线程必须是同一把锁)
同步的前提
多个线程
多个线程使用的是同一个锁对象
同步的好处
同步的出现解决了多线程的安全问题。
同步的弊端
当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
解决线程安全问题实现2:
同步方法
就是把同步关键字加到方法上
同步方法的锁对象是什么呢?(this)
如果是静态方法,同步方法的锁对象又是什么呢?(类的字节码文件(类名.class))
那么,我们到底使用谁?
如果锁对象是this,就可以考虑使用同步方法。
否则能使用同步代码块的尽量使用同步代码块。
public class Sellticket implements Runnable {
//定义一百张票
private int tickets = 100;
//定义锁对象
private Object obj = new Object();
@Override
public void run() {
while (true){
synchronized(obj) {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售" + (tickets--) + "张票");
}
}
}
}
}
public class RunnableDemo {
public static void main(String[] args){
/*
多线程Runnable接口方式卖电影票
*/
//创建资源对象
Sellticket st = new Sellticket();
//创建三个线程对象
Thread t1 = new Thread(st,"窗口1");
Thread t2 = new Thread(st,"窗口2");
Thread t3 = new Thread(st,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
同步弊端
效率低
如果出现了同步嵌套,就容易产生死锁问题
死锁问题及其代码
是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象
同步代码块的嵌套案例
线程间通信:针对同一个资源的操作有不同种类的线程
案例:通过设置线程(生产者)和获取线程(消费者)针对同一个学生对象进行操作
注意:要考虑线程安全问题
public class Student {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class SetThread implements Runnable{
private Student s;
public int x = 0;
public SetThread(Student s){
this.s = s;
}
@Override
public void run() {
while (true){
synchronized (s){
if (x % 2 == 0){
s.setName("林青霞");
s.setAge(27);
}else{
s.setName("Fairy");
s.setAge(20);
}
x ++;
}
}
}
}
public class GetThread implements Runnable {
private Student s;
public GetThread(Student s){
this.s = s;
}
@Override
public void run() {
while (true){
synchronized (s){
System.out.println(s.getName() +"------"+s.getAge());
}
}
}
}
public class StudentDemo {
public static void main(String[] args){
//创建资源
Student s = new Student();
//设置和获取类
SetThread setThread = new SetThread(s);
GetThread getThread = new GetThread(s);
//线程类
Thread t1 = new Thread(setThread);
Thread t2 = new Thread(getThread);
t1.start();
t2.start();
}
}
生产者和消费者正常思路:
A:生产者:
先看是否有数据,有就等待,没有就生产,生产完之后通知消费者来消费数据
B:消费者:
先是看是否有数据,有就消费,没有就等待,通知生产者生产数据
为了处理这样的问题,java就提供了一种机制:等待唤醒机制
等待唤醒:
Object类中提供了三个方法:
wait():等待
notify():唤醒单个线程
notifyAll():唤醒所有线程
public class Student {
private String name;
private int age;
boolean flag; // 默认是没有数据的
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class SetThread implements Runnable{
private Student s;
public int x = 0;
public SetThread(Student s){
this.s = s;
}
@Override
public void run() {
while (true){
synchronized (s){
//判断有没有数据
if(s.flag){
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (x % 2 == 0){
s.setName("林青霞");
s.setAge(27);
}else{
s.setName("Fairy");
s.setAge(20);
}
x ++;
//修改标记
s.flag = true;
//唤醒线程
s.notify();
}
}
}
}
public class GetThread implements Runnable {
private Student s;
public GetThread(Student s){
this.s = s;
}
@Override
public void run() {
while (true){
synchronized (s){
if (!s.flag){
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(s.getName() +"------"+s.getAge());
//修改标记
s.flag = false;
//唤醒线程
s.notify();
}
}
}
}
public class StudentDemo {
public static void main(String[] args){
//创建资源
Student s = new Student();
//设置和获取类
SetThread setThread = new SetThread(s);
GetThread getThread = new GetThread(s);
//线程类
Thread t1 = new Thread(setThread);
Thread t2 = new Thread(getThread);
t1.start();
t2.start();
}
}
线程组
Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。
默认情况下,所有的线程都属于主线程组。
public final ThreadGroup getThreadGroup()
我们也可以给线程设置分组
Thread(ThreadGroup group, Runnable target, String name)
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int x =0; x<100;x++){
System.out.println(Thread.currentThread().getName() + ":" + x);
}
}
}
/*
线程组:把多个线程组合到一起
*/
public class ThreadGroupDemo {
public static void main(String[] args){
// method1();
method2();
}
public static void method1(){
MyRunnable my = new MyRunnable();
Thread t1 = new Thread(my,"林青霞");
Thread t2 = new Thread(my,"Fairy");
//线程类里面的方法
ThreadGroup tg1 = t1.getThreadGroup();
ThreadGroup tg2 = t2.getThreadGroup();
//线程组里面的方法
String name1 = tg1.getName();
String name2 = tg2.getName();
System.out.println(name1);
System.out.println(name2);
//通过结果知道:线程默认情况下属于main线程组
//默认情况下,所有的线程数与同一个组
System.out.println(Thread.currentThread().getThreadGroup().getName());
//如何修改线程组:创建一个线程组,把其他线程的组指定为我们自己新建线程组
}
public static void method2(){
ThreadGroup tg = new ThreadGroup("这是一个新的线程组");
MyRunnable my = new MyRunnable();
Thread t1 = new Thread(tg,my,"林青霞");
Thread t2 = new Thread(tg,my,"Fairy");
System.out.println(t1.getThreadGroup().getName());
System.out.println(t2.getThreadGroup().getName());
//通过组名称设置后台线程,表示该组的线程都是后台线程
tg.setDaemon(true);
}
}
线程池
程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。
线程池的好处:线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。
public class MyRunnable implements Runnable {
@Override
public void run() {
for(int x = 0; x <= 100; x++){
System.out.println(Thread.currentThread().getName()+ ":" + x);
}
}
}
/*
如何实现线程的代码呢?
A:创建一个线程池对象,控制要创建几个线程对象
public static ExecutorService newFixedThreadPool(int nThreads)
B:这种线程可以执行:
可以执行Runnable对象或者Callable对象代表的线程
C:调用如下方法即可:
Future<?> submit(Runnable task)
<T> Future<T> submit(Callable<T> task)
*/
public class ExecutorsDemo {
public static void main(String[] args){
ExecutorService pool = Executors.newFixedThreadPool(2);
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
//结束线程池
pool.shutdown();
}
}
callable:好处: 可以有返回值 可以抛出异常弊端: 代码比较复杂,所以一般不用
采用callable接口实现多线程求和案例
public class MyCallable implements Callable<Integer> {
private int number;
public MyCallable(int number){
this.number = number;
}
@Override
public Integer call() throws Exception {
int sum = 0;
for (int x =0; x<=number;x++){
sum += x;
}
return sum;
}
}
public class CallableDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(2);
Future<Integer> f1 = pool.submit(new MyCallable(100));
Future<Integer> f2 = pool.submit(new MyCallable(200));
Integer t1 = f1.get();
Integer t2 = f2.get();
System.out.println(t1);
System.out.println(t2);
pool.shutdown();
}
}
匿名内部类方式使用多线程:
new Thread(){代码…}.start();
New Thread(new Runnable(){代码…}).start();
public class ThreadDemo {
public static void main(String[] args){
//继承Thread类来实现多线程
new Thread(){
public void run(){
for(int x = 0; x < 100; x ++){
System.out.println(Thread.currentThread().getName() + ":" + x);
}
}
}.start();
//实现Runnable接口来实现多线程
new Thread(new Runnable() {
@Override
public void run() {
for(int x = 0; x < 100; x ++){
System.out.println(Thread.currentThread().getName() + ":" + x);
}
}
}){}.start();
}
}
定时器:
定时器是一个应用十分广泛的线程工具,可用于调度多个定时任务以后台线程的方式执行。在Java中,可以通过Timer和TimerTask类来实现定义调度的功能