是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。
每个线程都有一个优先级,高优先级线程的执行优先于低优先级线程。每个线程都可以或不可以标记为一个守护程序。当某个线程中运行的代码创建一个新 Thread 对象时,该新线程的初始优先级被设定为创建线程的优先级,并且当且仅当创建线程是守护线程时,新线程才是守护程序。
多线程执行时,到底在内存中是如何运行的呢? 多线程执行时,在栈内存中,其实每一个执行线程都有一片自己所属的栈内存空间。进行方法的压栈和弹栈。当执行线程的任务结束了,线程自动在栈内存中释放了。但是当所有的执行线程都结束了,那么进程就结束了。
创建新执行线程有两种方法。 一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。接下来可以分配并启动该子类的实例。 例: 打印的方法
/**
* 第一种方法继承thread
*/
class MyThread extends Thread{
private int tid;
public MyThread(int tid){
this.tid=tid;
}
//启动需要重载run方法
@Override
public void run() {
try{
for (int i=0;i<10;i++) {
Thread.sleep(1000);//每个一秒打印一个
System.out.println(String.format("T%d:%d",tid,i));//打印0-10
}
}catch (Exception e){
e.printStackTrace();
}
}
}
//调用
public static void testThread(){
for(int i=0;i<10;i++){
new MyThread(i).start();//循环开启十条线程
}
创建线程的另一种方法是声明实现 Runnable 接口的类。该类然后实现 run 方法。然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动。
public static void testThread(){{
//第二种方法 实现runnable匿名内部类
for(int i = 0;i < 10; ++i){
final int tid=1;
new Thread(new Runnable() {
@Override
public void run() {
try{
for (int i=0;i<10;i++) {
// Random random = new Random();
Thread.sleep(1000);//每个一秒打印一个
System.out.println(String.format("T%d:%d",tid,i));//打印0-10
}
}catch (Exception e){
e.printStackTrace();
}
}
}).start();
}
}
public static void main(String[] args) {
testThread();
}
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。 总结: 实现Runnable接口比继承Thread类所具有的优势:
适合多个相同的程序代码的线程去共享同一个资源 可以避免java中的单继承的局限性。 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。
如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。 引发的问题 例如卖票 可能两个人买到了同一张票 那么怎么解决这个问题呢 要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复票与不存在票问题,Java中提供了同步机制(synchronized)来解决。
private static Object obj=new Object();
//锁
public static void testSynchronized1(){
//被锁锁住之后只能等这个对象执行完之后才能执行下一个
synchronized (obj){
try{
for (int i=0;i<10;i++) {
// Random random = new Random();
Thread.sleep(1000);//每个一秒打印一个
System.out.println(String.format("T3%d",i));//打印0-10
}
}catch (Exception e){
e.printStackTrace();
}
}
}
//锁
public static void testSynchronized2(){
synchronized (obj){
try{
for (int i=0;i<10;i++) {
// Random random = new Random();
Thread.sleep(1000);//每个一秒打印一个
System.out.println(String.format("T4%d",i));//打印0-10
}
}catch (Exception e){
e.printStackTrace();
}
}
}
public static void testSynchronized(){
for (int i=0;i<10;i++){
new Thread(new Runnable() {
@Override
public void run() {
testSynchronized1();
testSynchronized2();
}
}).start();
}
}
只有当一个锁运行完毕之后第二个才会运行 不会出现抢占。
BlockingQueue即阻塞队列 阻塞队列主要用在生产者/消费者的场景 负责生产的线程不断的制造新对象并插入到阻塞队列中,直到达到这个队列的上限值。队列达到上限值之后生产线程将会被阻塞,直到消费的线程对这个队列进行消费。 例:生产者与消费者 生产者
static class Producer implements Runnable{
private BlockingQueue<String> q;
//创建一个发事件的队列
public Producer(BlockingQueue<String> q){
this.q=q;
}
@Override
public void run() {
try{
for(int i=0;i<10;i++){
//向消息队列里面放数据 每隔一秒放一个
Thread.sleep(1000);
q.put(String.valueOf(i));
}
}catch(Exception e){
e.printStackTrace();
}
}
}
消费者
/**
* 一个消费者 那面放消息 这面消费消息
*/
static class Consumer implements Runnable{
private BlockingQueue<String> q;
//创建一个发事件的队列 阻塞队列
public Consumer(BlockingQueue<String> q){
this.q=q;
}
@Override
public void run() {
try {
while(true){
//获取当前线程 名字 把队列里面的东西取出来 如果没有就一直卡着
System.out.println(Thread.currentThread().getName()+q.take());
}
}catch (Exception e){
e.printStackTrace();
}
}
}
开启线程
/**
* 两件事情 一个线程能插入数据
* 两个线程能取数据
*/
public static void testBlockQueue(){
//初始化一个BlockingQueue指定它的容量大小为10
BlockingQueue<String> q=new ArrayBlockingQueue<String>(10);
//开启一条放东西的线程
new Thread(new Producer(q)).start();
//开启两条消费的线程
new Thread(new Consumer(q),"Consumer1").start();
new Thread(new Consumer(q),"Consumer2").start();
}
public static void main(String[] args) {
testBlockQueue();
}
生产者放进10个产品之后 消费者两个进程取出
负责消费的线程不断的从队列中消费对象,直到这个队列为空,当队列为空时,消费线程将会被阻塞,除非队列中有新的对象被插入。
用原子方式更新的 int 值 java中的运算操作,自增或自减,若没有进行额外的同步操作,在多线程环境下就是线程不安全的。 多线程并发共享这个变量时会出现问题 AtomicInteger原子操作则不会出现问题 演示 定义了一个普通的变量和AtomicInteger 原子操作类 定义了一个随机睡眠的方法
private static int counter=0;
private static AtomicInteger atomicInteger=new AtomicInteger(0);
//定义一个睡眠的方法
public static void sleep(int mills){
try{
Thread.sleep(new Random().nextInt(mills));//随机睡眠的方法
}catch (Exception e){
e.printStackTrace();
}
}
原子操作增加
/**
* 对一个变量读写的操作
* AtomicIntegerAtomicInteger
* 一个提供原子操作的Integer的类。
* 在Java语言中,++i和i++操作并不是线程安全的,
* 在使用的时候,不可避免的会用到synchronized关键字。而AtomicInteger则通过一种线程安全的加减操作接口。
* public final int get() //获取当前的值
* public final int getAndSet(int newValue)//获取当前的值,并设置新的值
* public final int getAndIncrement()//获取当前的值,并自增
* public final int getAndDecrement() //获取当前的值,并自减
* public final int getAndAdd(int delta) //获取当前的值,并加上预期的值
*/
public static void testWithAtomic(){
for (int i=0;i<10;++i){
new Thread(new Runnable() {
@Override
public void run() {
sleep(1000);//随机在一秒以内的时间睡眠
for (int j=0;j<10;++j){
System.out.println(atomicInteger.incrementAndGet());//incrementAndGet()以原子方式将当前值加 1。
}
}
}).start();
}
}
普通自增
/**
*利用多线程的变量加减
*/
public static void testWithoutAtomic(){
for (int i=0;i<10;++i){
new Thread(new Runnable() {
@Override
public void run() {
sleep(1000);//随机在一秒以内的时间睡眠
for (int j=0;j<10;++j){
counter++;//java里的运算(比如自增)并不是原子性的。
System.out.println(counter);
}
}
}).start();
}
}
测试
public static void testAtomic(){
//测试原子的
testWithAtomic();
// testWithoutAtomic();
}
每次都能输出到100 而自增会出现达不到100的状况