1.进程
正在运行的程序就是一个进程(任务管理器),进程是系统分配资源调用的一个独立单位
多进程
现在计算机是一个多进程计算机,即在做一件事时还可以做另一件事。
可以提高CPU的使用率
2.线程
线程依赖于进程而存在,一个线程相当于进程中的某个任务。
多线程
一个进程开启多个任务,每一个任务(线程),他们在抢占CPU的执行权,线程的执行具有随机性。
3.并发(concurrency): 指一个处理器同时处理多个任务。指的是同一时间点
4.并行(parallel):指多个处理器或者是多核的处理器同时处理多个不同的任务,指的是同一时间段
5.Java虚拟机是多线程程序吗?
是,由于java虚拟机中自带一个垃圾回收器,确保程序不会轻易的造成内存溢出!至少会开启两条子线程,当前程序在执行的时候会开启main:主线程,垃圾回收器开启一个垃圾回收线程,来确保程序不会内存异常,将不用的变量或者没有更多引用的对象回收掉。
6.多线程实现的方式一:继承Thread类
要实现对线程,必须创建一个进程,创建进程必须调用系统资源,但Java不能直接调用系统资源,C/C++语言可以创建系统资源,然后java使用封装好的C/C++(Thread类)。
启动线程不是调用run()方法,start()方法是线程开始执行的方法,run()方法只是一个普通的方法,不能实现线程的随机性;而start()方法调用是通过JVM调用线程中的run()方法来进行多个线程抢占CPU执行权。
package thread;
/**
* 多线程程序实现方式1:
* 1)自定一个类:MyThread 继承自Thread类
* 2)在MyThread类中重写Thread类中的run() :为什么重写run()
* 3)在主线程中,创建该类的实例对象,启动线程
*
*2017年12月6日
*/
public class MyThread extends Thread{
//run()方法里面执行一些耗时操作,循环,线程睡眠,线程等待
public void run(){
for(int i=0;i<200;i++){
System.out.println(i);
}
}
}
package thread;
public class MyThreadDemo {
public static void main(String[] args) {
//创建多线程对象
MyThread mt1 = new MyThread();
MyThread mt2 = new MyThread();
//启动多线程
mt1.start();
mt2.start();
}
}
7.多线程相关的方法
1)getName()获取线程名称
设置线程名称:有参构造 无参+setName(String name)方法
2)setPriority()设置线程优先级
3)public final int getPriority()返回线程的优先级
默认优先级5;做大优先级10;最小优先级1
注意:final修饰类:不能被继承;修饰成员方法:不能被重写;成员变量:一个常量
4)public final void join() 等待线程终止(必须先启动线程)
throws InterruptedException
理解:例如创建jh1 jh2 jh3三个线程对象,jh1启动线程后调用该方法,则jh2 jh3必须在jh1结束了才执行
5)public static void yield()暂停当前正在执行的线程对象,并执行其他线程,并不保证其他线程可以抢占到CPU的执行权
6)public static Thread currentThread():表示正在运行的线程
7)public final voidsetDaemo(boolean on) on指定为true:守护线程,当正在运行的线程是守护线程时虚拟机退出,该方法在启动前调用。
8)public final void stop():强迫线程停止运行
9)public voidinterrupt():中断线程
10)public static void sleep(long millis):在指定的时间毫秒内让该线程休眠
throws InterruptException
重点掌握:sleep()线程休眠和setDaemo(boolean on)守护线程
package sleepthread;
//import java.text.SimpleDateFormat;
import java.util.Date;
public class SleepThread extends Thread{
/*//格式化日期对象
Date d = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String datestr = sdf.format(d);*/
@Override
public void run() {
/*Date d = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String datestr = sdf.format(d);*/
for(int i=0;i<100;i++){
System.out.println(getName()+":"+i+"-"+new Date());
try {
//线程休眠一秒
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
package sleepthread;
public class SleepDemo {
public static void main(String[] args) {
//创建线程对象
SleepThread sd1 = new SleepThread();
SleepThread sd2 = new SleepThread();
//设置名字
sd1.setName("论论论");
sd2.setName("催催催");
//启动线程
sd1.start();
sd2.start();
}
}
package setDaemonthread;
public class DaemonThread extends Thread{
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println(getName()+":"+i);
}
}
}
package setDaemonthread;
/*
* public final void setDaemon(boolean on) on指定true,就是设置守护线程...
* 将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。
该方法必须在启动线程前调用。
jvm自动退出,对于主线程的数据如果直接输出完毕,对于两个守护线程来说不会立即消失,Jvm等会就自动退出.
比如:坦克大战
* */
public class DaemonDemo {
public static void main(String[] args) {
//创建线程类对象
DaemonThread td1 = new DaemonThread();
DaemonThread td2 = new DaemonThread();
//设置线程名称
td1.setName("罗");
td2.setName("李");
//setDaemon(boolean on),将该线程设置为守护线程
td1.setDaemon(true);
td2.setDaemon(true);
//启动线程
td1.start();
td2.start();
}
}
8.多线程实现的方式二(实现Runnable接口)
开发步骤:1)自定义一个类实现Runnable接口
2)实现该接口中的run()方法
3)在主线程中创建该类的实例对象
4)创建Thread类对象,将3)创建的对象作为参数传递
5)分别启动线程
package runnable;
public class MyRunnable implements Runnable{
@Override
public void run() {
for(int i=0;i<200;i++){
//System.out.println(getName()+":"+i);//会报错,因为getName()是Thread类中的方法
//可以间接使用Thread类中的静态方法获取线程名称
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
package runnable;
public class RunnableDemo {
public static void main(String[] args) {
//创建MyRunnable实例对象
MyRunnable my = new MyRunnable();
//创建Thread类对象
Thread t1 = new Thread(my,"线程1");
Thread t2 = new Thread(my,"线程2");
//启动线程
t1.start();
t2.start();
}
}
问题:为什么有了第一种实现方法还有第二种?
继承的方式中,由于单继承具有局限性;第二种方式更符合java的设计原则及设计模式(数据分离原则!)Myrunnable对象作为两个子线程的共享对象(共享数据),java多线程实现中接口大于继承的方式。
实例:模拟电影院卖票
package runnable;
public class TicketsRunnableTest implements Runnable {
private static int tickets = 100;
@Override
public void run() {
while(true){
if(tickets>0){
//结果会出现负票,加入同步机制可改善
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets--+"张票");
}
}
}
}
package runnable;
public class TicketsRunnableDemo {
public static void main(String[] args) {
//创建TicketsRunnableTest对象
TicketsRunnableTest tr = new TicketsRunnableTest();
//创建线程类类对象
Thread t1 = new Thread(tr,"窗口1");
Thread t2 = new Thread(tr,"窗口2");
Thread t3 = new Thread(tr,"窗口3");
//启动线程
t1.start();
t2.start();
t3.start();
}
}
中间省略100-20的票
分析:(由于加入睡眠,延迟和线程的随机性导致的)可以看到打印的票数为负数,这样就出现问题,由于同时创建了3个线程,这三个线程执行run()方法,在tickets变为1时,线程1、2、3都对tickets变量有存储功能,当窗口1执行run()方法时,还没来的及做递减操作,就指定它调用sleep方法进入就绪状态,这时窗口2、3都进入了run()方法,发现tickets变量依然大于0,但此时窗口1的休眠时间已到,将tickets变量值递减,同时窗口2、3也对num变量进行递减操作,从而产生赋值。
问题:如何解决多线程的安全问题?
使用同步代码块解决线程安全问题
问题:检查一个多线程是否有安全问题多的标准?
是否是多线程环境 是否有共享数据 是否多条语句对共享数据操作
9.Java同步机制
同步机制可以有效防止资源冲突,同步机制使用synchronized关键字
同步代码块:
syschronized(同步锁对象){
多条语句对共享数据操作
}
同步锁对象:
1)可以是任意的类或Object类的对象
将共享资源的操作放在synchronized定义的区域内,当其他线程也获得这个锁时,必须等待锁被释放时才能进入该区域。Object为任一个对象,每个对象都存在一个标志位,并且具有两个值0和1。一个线程运行到同步代码块时首先检查该对象的标志位,如果为0状态,表明此同步代码块中存在其他线程正在运行。这时线程处于就绪状态,直到处于同步代码块中的线程执行完同步代码块的代码为止。这时该对象的标志位被设置为1,该线程才能执行同步代码块中的代码,并将Object对象的标志位设置为0,防止其他线程执行同步块中的代码。
package runnable;
public class TicketsRunnableTest implements Runnable {
private static 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--+"张票");
}
}
}
}
}
package runnable;
public class TicketsRunnableDemo {
public static void main(String[] args) {
//创建TicketsRunnableTest对象
TicketsRunnableTest tr = new TicketsRunnableTest();
//创建线程类类对象
Thread t1 = new Thread(tr,"窗口1");
Thread t2 = new Thread(tr,"窗口2");
Thread t3 = new Thread(tr,"窗口3");
//启动线程
t1.start();
t2.start();
t3.start();
}
}
2)如果一个方法进来之后是同步代码块,那么同步代码块可以演变成一个同步方法,默认的锁对象(this)
public/private synchronized 返回值类型 方法名(参数){
}
当某个对象调用同步方法时,该对象上的其他同步方法必须等待该同步方法执行完毕后才能被执行。必须将每个能访问共享资源的方法修饰为synchronized,否则会出错。
package selltickets;
public class SellTickets implements Runnable{
private static int tickets = 100;
//同步方法(非静态)它的同步锁对象是this
public synchronized void sellTicket() {
if(tickets>0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (tickets--) + "张票");
}
}
@Override
public void run() {
//模拟电影院售票,假设一直有票
while(true){
sellTicket();
//如果一个方法进来之后是同步代码块,那么该方法可以演变成同步方法
}
}
}
package selltickets;
public class SellTicketsDemo {
public static void main(String[] args) {
//创建TicketsRunnableTest对象
SellTickets st = new SellTickets();
//创建线程类类对象
Thread t1 = new Thread(st,"窗口1");
Thread t2 = new Thread(st,"窗口2");
Thread t3 = new Thread(st,"窗口3");
//启动线程
t1.start();
t2.start();
t3.start();
}
}
3)如果一个静态的同步方法,锁对象是当前class属性
public static synchronized 返回值类型 方法名(){
}
package staticselltickets;
public class SellTickets implements Runnable{
private static int tickets = 100;
@Override
public void run() {
//模拟电影院售票,假设一直有票
while(true){
sellTickets();
}
}
//静态的同步方法,锁对象是当前class属性:类名.class(反射机制)获取一些类的字节码文件
public static synchronized void sellTickets() {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (tickets--) + "张票");
}
}
}
package staticselltickets;
public class SellTicketsDemo {
public static void main(String[] args) {
//创建SellTickets对象
SellTickets st = new SellTickets();
//创建线程类类对象
Thread t1 = new Thread(st,"窗口1");
Thread t2 = new Thread(st,"窗口2");
Thread t3 = new Thread(st,"窗口3");
//启动线程
t1.start();
t2.start();
t3.start();
}
}